如何取消Stream.ReadAsync?

时间:2017-02-17 19:20:26

标签: c# c#-4.0 async-await cancellation

如果您没有输入任何输入,为什么以下代码没有完成?为什么即使取消令牌取消后它仍然会响应按键?

// 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

The documentation for Stream.ReadAsync says

  

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

这意味着取消取消令牌将取消操作,对吧?但出于某种原因the source code for Stream.ReadAsync如果事先没有取消,则{@ 3}}对取消令牌不做任何事情:

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);
}

因此,取消令牌参数毫无意义 - 如何取消该异步读取?

2 个答案:

答案 0 :(得分:0)

在Console输入的特定情况下,似乎没有其他方法可以轮询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");
}

对我而言,这表明您无法以一般方式可靠地使用Stream.ReadAsync,因为您必须根据您正在处理的Stream实现做不同的事情。

编辑:

考虑到这一点,有意义的是你无法取消ReadAsync,因为Stream抽象类没有任何处理异步操作的抽象方法;实现Stream所必须做的就是实现一些getter和一些阻塞方法,这是Microsoft在__ConsoleStream类中所做的全部。

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

因此,Microsoft应该已经删除了取消令牌参数,或者应该将抽象的异步可取消方法放入Stream类中,以便使__ConsoleStream的人员被迫实施它们。 / p>

答案 1 :(得分:0)

马特,我有同样的问题。如果您使用

,我找不到取消它的方法
Port.BaseStream.ReadAsync()

如果无法读取所需的字节数,那么我发现能够使用

再次读取端口的唯一方法
Port.BaseStream.ReadAsync()

此事件后要致电

Port.DiscardInBuffer()Port.BaseStream.ReadAsync()之后

无法读取所需的字节数。然后,您可以再次写入端口以请求要再次读取的数据并使用

正确读取
Port.Basestream.ReadAsync()
再次

假定可以读取正确的字节数。我成功地使用它实现了Retry函数。另一种可行的方法是通过检查

来检查是否有可读取的字节
 Port.BytesToRead = 0

如果是这样,则不要呼叫

Port.BaseStream.ReadAsync()