How to cancel Stream.ReadAsync?

守給你的承諾、 提交于 2019-12-23 01:52:58

问题


Why doesn't the code below ever complete if you don't type any input, and why does it still respond to a key being pressed even after the cancellation token has been canceled?

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

If the operation is canceled before it completes, the returned task contains the Canceled value for the Status property.

This implies that canceling the cancellation token will cancel the operation, right? Yet for some reason the source code for Stream.ReadAsync doesn't do anything with the cancellation token if it isn't canceled beforehand:

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

Therefore the cancellation token parameter is pointless--how can I cancel that async read?


回答1:


In the particular case of Console input, there appears to be no other way than to poll the Console.KeyAvailable property:

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

To me, this suggests that you cannot reliably use Stream.ReadAsync in a general way, because you must do different things depending on which implementation of Stream you're dealing with.

Edit:

Thinking about this a little more, it makes sense that you can't cancel ReadAsync, because the Stream abstract class does not have any abstract methods dealing with asynchronous operations; all you must do to implement a Stream is implement some getters and some blocking methods, which is all Microsoft has done with the __ConsoleStream class.

Since the only methods that can be guaranteed to exist on a Stream are blocking methods, and since it's impossible to cancel a blocking invocation (you can't even do a blocking IO operation on another thread, cancel the thread, and have the operation stop), it's impossible to have cancelable asynchronous operations.

Therefore Microsoft either should have removed the cancellation token parameter, or should have put abstract asynchronous cancelable methods into the Stream class so that the folks who made __ConsoleStream would have been forced to implement them.




回答2:


Matt, I had the same problem. I could not find a way to cancel it.

If you use Port.BaseStream.ReadAsync() and if fails to read the required number of bytes, then the only way I found to be able to read the port again using Port.BaseStream.ReadAsync() after this event is to call Port.DiscardInBuffer() after Port.BaseStream.ReadAsync() failed to read the required number of bytes.

Then you can write to the port again to request the data you want to read again and read it correctly using Port.Basestream.ReadAsync() again assuming the correct number of bytes are available to be read.

I used this successfully it implement a Retry function.

The other way that could work would be to check if there are bytes to available read by checking if Port.BytesToRead == 0 and if this is true then do not call Port.BaseStream.ReadAsync().



来源:https://stackoverflow.com/questions/42305825/how-to-cancel-stream-readasync

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!