问题
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