与CancellationTokenSource的Task.Factory.FromAsync

我有以下代码行用于从NetworkStream异步读取:

int bytesRead = await Task.Factory.FromAsync(this.stream.BeginRead, this.stream.EndRead, buffer, 0, buffer.Length, null); 

我想支持取消。 我看到我可以使用CancellationTokenSource取消任务 ,但是我没有看到任何方法可以将它传递给TaskFactory.FromAsync() 。

是否可以使FromAsync()构造的任务支持取消?

编辑:我想取消已经运行的任务。

Gigi,遗憾的是FromAsync的语义特性表明你只是将异步过程改编为TPL的API(TPL = Microsoft的任务并行库 )

本质上,TPL的ReadAsync控制异步行为本身,而FromAsync仅包装行为(但不控制它)。

现在因为Cancellation是一个特定于TPL的构造,并且由于FromAsync无法控制被调用的异步方法的内部工作,因此无法保证干净地取消任务并确保所有资源都正确关闭(这就是为什么它如果你很好奇,只需反编译方法;))

在这些情况下,将实际异步调用自己包装在正常任务中并检测OperationCancelledexception更有意义,这将使您有机会通过进行适当的调用来关闭流。

简而言之,答案是否定的 ,但没有什么可以阻止您创建一个通用的重载方法,该方法将根据其类型选择正确的策略来干净地关闭流。

不,没有通用的方法来取消这样的任务。 取消是特定于API的。

  • 例如, WebClient有一个Cancel方法。
  • SocketFileStream需要Close以取消未完成的呼叫。
  • Web服务客户端甚至有不同的中止呼叫方式。

这是因为IO操作的实现者必须支持取消。

使用NetworkStream.ReadAsync并传递取消令牌似乎很诱人但是Stream.ReadAsync 。 后者只是抛弃了令牌。 基本上不支持。

Stream.ReadAsync只是基类方法。 它本身没有任何作用。 具体IO操作仅由派生类别发布。 那些必须支持原生取消。 Stream无法阻止它们。 碰巧NetworkStream不支持取消。

我知道您要取消操作并保持套接字打开。 但这是不可能的。 (主观注意:这实际上是一种悲惨的状态。特别是考虑到Windows在Win32级别支持可取消的IO。)

如果您仍希望应用程序快速继续,虽然IO操作不可取消,但只需忽略该任务的结果并继续。 请注意,IO最终可能会完成,例如从套接字缓冲区中排出数据或导致其他副作用。

“忽略取消”有效地使流位置不确定。 流变得无法使用。 这并不能真正避免打开新流。 您仍然必须摆脱旧流(在大多数情况下)并重新打开。 此外,您正在引入并发。

正如其他人已经提到的那样,没有干净的方法来实现你所要求的。 异步编程模型中没有取消的概念; 因此,无法通过FromAsync转换器进行改装。

但是,您可以为包装异步操作的Task引入取消。 这不会取消底层操作本身 – 您的NetworkStream仍将继续从套接字读取所有请求的字节 – 但它将允许您的应用程序作出反应,就像操作被取消一样,立即从您的await抛出OperationCanceledException (并执行任何已注册的)任务延续)。 一旦完成,底层操作的结果将被忽略。

这是一个辅助扩展方法:

 public static class TaskExtensions { public async static Task HandleCancellation( this Task asyncTask, CancellationToken cancellationToken) { // Create another task that completes as soon as cancellation is requested. // http://stackoverflow.com/a/18672893/1149773 var tcs = new TaskCompletionSource(); cancellationToken.Register(() => tcs.TrySetCanceled(), useSynchronizationContext: false); var cancellationTask = tcs.Task; // Create a task that completes when either the async operation completes, // or cancellation is requested. var readyTask = await Task.WhenAny(asyncTask, cancellationTask); // In case of cancellation, register a continuation to observe any unhandled // exceptions from the asynchronous operation (once it completes). // In .NET 4.0, unobserved task exceptions would terminate the process. if (readyTask == cancellationTask) asyncTask.ContinueWith(_ => asyncTask.Exception, TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously); return await readyTask; } } 

这是一个使用扩展方法将操作视为300ms后取消的示例:

 CancellationTokenSource cts = new CancellationTokenSource(); cts.CancelAfter(TimeSpan.FromMilliseconds(300)); try { int bytesRead = await Task.Factory.FromAsync(this.stream.BeginRead, this.stream.EndRead, buffer, 0, buffer.Length, null) .HandleCancellation(cts.Token); } catch (OperationCanceledException) { // Operation took longer than 300ms, and was treated as cancelled. }