1

编辑:我正在清理描述,因为我已经确定这也会影响WriteAsync,而不仅仅是ReadAsync......

如果其中一个调用当前正在阻塞——ReadAsync因为通道是空的,或者WriteAsync因为通道已满——那么发出取消令牌的信号不会导致向调用者返回执行。即它不返回值也不抛出。它只会永远阻塞。从另一个线程调用Complete通道将导致阻塞调用 throw ChannelClosedException,但我不清楚为什么发出信号的取消令牌是不够的。

为了进一步增加混淆,该代码实际上像 .NET Fiddle 一样工作,但在 Visual Studio 2019 内部或命令提示符下(两者都在 Windows 10 x64 上)不起作用。

在下面的示例代码中,取消注释Completemain 中的行将允许彻底关闭,但没有它,对 never 的调用将WriteAsync不会返回,因此对 never的调用也会Task.WaitAll返回。

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;


public class Program
{
    public static async Task Task1(Channel<String> q1, CancellationToken cancellationToken)
    {
        int idx = 0;

        Console.WriteLine($"Task1 starting");

        while (!cancellationToken.IsCancellationRequested)
        {
            try
            {
                await Task.Delay(100);

                string s = $"element{idx++}";

                Console.WriteLine($"Calling write on {s}");
                await q1.Writer.WriteAsync(s, cancellationToken);
                Console.WriteLine($"Write returned for {s}");
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine($"Operation was cancelled");
            }
            catch (ChannelClosedException)
            {
                Console.WriteLine($"Channel was closed");
            }
        }

        //q1.Writer.Complete();
        Console.WriteLine($"Task1 stopping");

    }

    public static async Task Main()
    {
        Console.WriteLine($"Main started");

        var tasklist = new List<Task>();

        var q1 = Channel.CreateBounded<String>(
            new BoundedChannelOptions(10)
            {
                AllowSynchronousContinuations = false,
                SingleReader = true,
                SingleWriter = true,
                FullMode = BoundedChannelFullMode.Wait
            });

        var cts = new CancellationTokenSource(5000);

        tasklist.Add(Task.Run(() => Task1(q1, cts.Token), cts.Token));

        while (!cts.Token.IsCancellationRequested)
        {
            try
            {
                await Task.Delay(10000, cts.Token);
            }
            catch (OperationCanceledException) { }
        }

        //q1.Writer.Complete();

        Console.WriteLine($"Waiting for all tasks to terminate");
        Task.WaitAll(tasklist.ToArray(), CancellationToken.None);
        Console.WriteLine($"All tasks terminated");
    }
}

和输出:

Main started
Task1 starting
Calling write on element0
Write returned for element0
Calling write on element1
Write returned for element1
Calling write on element2
Write returned for element2
Calling write on element3
Write returned for element3
Calling write on element4
Write returned for element4
Calling write on element5
Write returned for element5
Calling write on element6
Write returned for element6
Calling write on element7
Write returned for element7
Calling write on element8
Write returned for element8
Calling write on element9
Write returned for element9
Calling write on element10
Waiting for all tasks to terminate
4

1 回答 1

1

事实证明,这根本不是频道的问题。它与导致死锁的取消令牌的串行同步处理有关。完成通道可以避免这个问题,就像向主服务员添加一个 Task.Yield 一样。在此处查看更多信息: https ://github.com/dotnet/runtime/issues/64051

于 2022-01-25T01:51:35.573 回答