与CancellationTokenSource的同步

本文关键字:同步 CancellationTokenSource | 更新日期: 2023-09-27 18:14:58

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

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

我想让它支持取消。我看到我可以使用CancellationTokenSource取消任务,但是我看不到我可以将其传递给TaskFactory.FromAsync()的任何方式。

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

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

与CancellationTokenSource的同步

Gigi,不幸的是FromAsync的语义性质表明,你只是将异步进程适应TPL的API (TPL =微软的任务并行库)

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

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

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

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

正如其他人已经提到的,没有干净的方法可以实现您的要求。异步编程模型中没有取消的概念;因此,它不能通过FromAsync转换器进行改造。

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

这是一个帮助器扩展方法:

public static class TaskExtensions
{
    public async static Task<TResult> HandleCancellation<TResult>(
        this Task<TResult> asyncTask,
        CancellationToken cancellationToken)
    {     
        // Create another task that completes as soon as cancellation is requested.
        // http://stackoverflow.com/a/18672893/1149773
        var tcs = new TaskCompletionSource<TResult>();
        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;
    }
}

下面是一个使用extension方法将300ms后的操作视为取消的例子:

CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromMilliseconds(300));
try
{
    int bytesRead = 
        await Task<int>.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.
}

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

  • 例如,WebClientCancel方法
  • SocketFileStream需要Close 'd来取消未完成的呼叫
  • web服务客户端甚至有不同的终止调用的方式。

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

使用NetworkStream.ReadAsync并传递一个取消令牌似乎很诱人,但Stream.ReadAsync才是。后者只是把代币扔掉。基本上不支持。

Stream.ReadAsync只是基类方法。它自己不做任何事情。具体的IO操作只能由派生类发出。它们必须本地支持取消。流不能做任何事情来强迫他们。碰巧NetworkStream不支持取消

我明白您想取消操作并让套接字保持打开状态。但这是不可能的。(主观上说:这真是一件可悲的事情。特别是考虑到Windows在Win32级别支持可取消的IO。

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

" cancel by ignore "有效地使流位置未定义。流不可用。这并不能真正避免打开新流的需要。你仍然需要摆脱旧的流(在大多数情况下)并重新打开。而且,您还引入了并发性。