问题
In .Net 4.5 there exists a method on the Stream class which read asynchronously from a stream while monitoring the cancellation token.
ReadAsync :
buffer:byte[] *
offset:int *
count:int *
cancellationToken:CancellationToken -> Task<int>
If another thread should first trigger the cancellation token then closes the stream, then is it guaranteed that the reading thread will be cancelled before ReadAsync throws an exception?
Can I somehow achieve this guarantee using the .Net 4.0 framework and F# asynchronous workflows without ReadAsync(which has no overload which accepts a cancellation token to be monitored)?
回答1:
This particular overload is basically useless unless you're chaining multiple tasks together – cancellationToken
is only checked upon entry to the ReadAsync
method call, and not while the underlying Stream.BeginRead
call is executing.
Code dumped from ILSpy:
public virtual Task<int> ReadAsync(byte[] buffer,
int offset,
int count,
CancellationToken cancellationToken)
{
if (!cancellationToken.IsCancellationRequested)
return this.BeginEndReadAsync(buffer, offset, count);
return Task.FromCancellation<int>(cancellationToken);
}
As you can see, cancellationToken
is not forwarded into the BeginEndReadAsync
call, and BeginEndReadAsync
is merely implemented in terms of Stream.BeginRead
:
private Task<int> BeginEndReadAsync(byte[] buffer, int offset, int count)
{
return TaskFactory<int>.FromAsyncTrim<Stream, Stream.ReadWriteParameters>(
this,
new Stream.ReadWriteParameters
{
Buffer = buffer,
Offset = offset,
Count = count
},
(Stream stream, Stream.ReadWriteParameters args, AsyncCallback callback, object state) =>
stream.BeginRead(args.Buffer, args.Offset, args.Count, callback, state),
(Stream stream, IAsyncResult asyncResult) =>
stream.EndRead(asyncResult)
);
}
At this point, the only guarantees you have are those made by the derived stream type, which vary from type to type.
Note that this is based on the current .Net 4.5 bits, and the implementation is of course subject to change in the future.
回答2:
You can check Task.IsCanceled before running the task with Result
/RunSynchronously
.
Here's some example code:
use stream = new MemoryStream(Array.init 1000 (fun i -> byte (i % int Byte.MaxValue)))
use waitHandle = new EventWaitHandle(false, EventResetMode.ManualReset)
use cts = new CancellationTokenSource()
let thread = Thread(fun () ->
Thread.Sleep(1000)
let buf = Array.zeroCreate 100
let task = stream.ReadAsync(buf, 0, buf.Length, cts.Token)
if not task.IsCanceled then task.RunSynchronously()
waitHandle.Set() |> ignore)
thread.Start()
cts.Cancel()
waitHandle.WaitOne() |> ignore
But once ReadAsync
begins, it will throw an AggregateException
stating the task has been canceled.
回答3:
AFAIK, you have to both use a mutex to protect the stream and use the cancellation token to check the thread for pending cancellation. There is not asynchronous primitive on Stream
that handles this for you.
来源:https://stackoverflow.com/questions/12608728/emulating-readasync