Emulating ReadAsync

时光怂恿深爱的人放手 提交于 2019-12-12 21:16:25

问题


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

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