如何取消 Stream.ReadAsync?

Mat*_*mas 5 c# cancellation async-await c#-4.0

如果您不输入任何输入,为什么下面的代码永远不会完成,为什么即使在取消标记被取消后它仍然响应按下的键?

// Set up a cancellation token
var cancellationSource = new CancellationTokenSource();

// Cancel the cancellation token after a little bit of time
Task.Run(async () =>
{
    await Task.Delay(TimeSpan.FromSeconds(2));
    cancellationSource.Cancel();
    Console.WriteLine("Canceled the cancellation token");
});

// Wait for user input, or the cancellation token
Task.Run(async () =>
{
    try
    {
        using (var input = Console.OpenStandardInput())
        {
            var buffer = new byte[1];
            Console.WriteLine("Waiting for input");
            await input.ReadAsync(buffer, 0, 1, cancellationSource.Token); // This is impossible to cancel???
            Console.WriteLine("Done waiting for input"); // This never happens until you press a key, regardless of the cancellation token
        }
    }
    catch (Exception e)
    {
        Console.WriteLine(e.Message); // No errors
    }
})
.Wait(); // Block until complete
Run Code Online (Sandbox Code Playgroud)

Stream.ReadAsync 的文档说

如果操作在完成之前被取消,则返回的任务将包含 Status 属性的 Canceled 值。

这意味着取消取消令牌将取消操作,对吗?然而由于某种原因如果没有事先取消,Stream.ReadAsync 的源代码不会对取消令牌做任何事情:

public virtual Task<int> ReadAsync(Byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
    // If cancellation was requested, bail early with an already completed task.
    // Otherwise, return a task that represents the Begin/End methods.
    return cancellationToken.IsCancellationRequested
                ? Task.FromCancellation<int>(cancellationToken)
                : BeginEndReadAsync(buffer, offset, count);
}
Run Code Online (Sandbox Code Playgroud)

因此取消令牌参数毫无意义——我如何取消异步读取?

Mat*_*mas 2

在控制台输入的特定情况下,除了轮询 Console.KeyAvailable 属性之外似乎没有其他方法:

var buffer = new byte[1];
Console.WriteLine("Waiting for input");

while (!Console.KeyAvailable && !cancellationSource.Token.IsCancellationRequested)
    await Task.Delay(10); // You can add the cancellation token as a second parameter here, but then canceling it will cause .Delay to throw an exception

if (cancellationSource.Token.IsCancellationRequested)
{
    Console.WriteLine("Canceled; no longer waiting for input");
}
else
{
    await input.ReadAsync(buffer, 0, 1);
    Console.WriteLine("Got user input");
}
Run Code Online (Sandbox Code Playgroud)

对我来说,这表明您无法Stream.ReadAsync以通用方式可靠地使用,因为您必须根据您正在处理的 Stream 实现来执行不同的操作。

编辑:

再仔细考虑一下,您不能取消取消是有道理的ReadAsync,因为Stream抽象类没有任何处理异步操作的抽象方法;要实现 a ,您所要做的Stream就是实现一些 getter 和一些阻塞方法,这就是 Microsoft 对该__ConsoleStream类所做的全部工作。

由于唯一可以保证存在于 Stream 上的方法是阻塞方法,并且由于不可能取消阻塞调用(您甚至无法在另一个线程上执行阻塞 IO 操作、取消线程并让操作停止) ),不可能有可取消的异步操作。

因此,微软要么应该删除取消标记参数,要么应该将抽象的异步可取消方法放入类中,Stream以便制作者__ConsoleStream被迫实现它们。