繁体   English   中英

取消不接受CancellationToken的异步操作的正确方法是什么?

[英]What is the correct way to cancel an async operation that doesn't accept a CancellationToken?

取消以下内容的正确方法是什么?

var tcpListener = new TcpListener(connection);
tcpListener.Start();
var client = await tcpListener.AcceptTcpClientAsync();

简单地调用tcpListener.Stop()似乎会导致ObjectDisposedException ,并且AcceptTcpClientAsync方法不接受CancellationToken结构。

我是否完全缺少明显的东西?

假设您不想在TcpListener上调用Stop方法 ,那么这里没有完美的解决方案。

如果您可以在某个时间段内未完成操作时收到通知,但允许原始操作完成,那么可以创建一个扩展方法,如下所示:

public static async Task<T> WithWaitCancellation<T>( 
    this Task<T> task, CancellationToken cancellationToken) 
{
    // The tasck completion source. 
    var tcs = new TaskCompletionSource<bool>(); 

    // Register with the cancellation token.
    using(cancellationToken.Register( s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs) ) 
    {
        // If the task waited on is the cancellation token...
        if (task != await Task.WhenAny(task, tcs.Task)) 
            throw new OperationCanceledException(cancellationToken); 
    }

    // Wait for one or the other to complete.
    return await task; 
}

以上摘自Stephen Toub的博客文章“如何取消不可取消的异步操作?”

这里需要说明的值得重复,这实际上并没有取消操作,因为没有了过载AcceptTcpClientAsync方法 ,需要一个CancellationToken ,它不能被取消。

这意味着,如果扩展方法指示确实发生了取消操作,那么您将取消对原始Task的回调的等待, 而不是取消操作本身。

为此,这就是为什么我将方法从WithCancellation重命名为WithCancellationWithWaitCancellation以指示您要取消的是wait ,而不是实际的操作。

从那里,很容易在您的代码中使用:

// Create the listener.
var tcpListener = new TcpListener(connection);

// Start.
tcpListener.Start();

// The CancellationToken.
var cancellationToken = ...;

// Have to wait on an OperationCanceledException
// to see if it was cancelled.
try
{
    // Wait for the client, with the ability to cancel
    // the *wait*.
    var client = await tcpListener.AcceptTcpClientAsync().
        WithWaitCancellation(cancellationToken);
}
catch (AggregateException ae)
{
    // Async exceptions are wrapped in
    // an AggregateException, so you have to
    // look here as well.
}
catch (OperationCancelledException oce)
{
    // The operation was cancelled, branch
    // code here.
}

请注意,必须取消对客户端的调用,以捕获在取消等待后抛出的OperationCanceledException实例。

我还抛出了AggregateException异常,因为从异步操作抛出异常时包装了异常(在这种情况下,您应该自己进行测试)。

面对诸如Stop方法之类的方法 (基本上,不管发生了什么事情,它都会剧烈地破坏一切),哪种方法才是更好的方法,这当然取决于您的情况。

如果您不共享正在等待的资源(在本例中为TcpListener ),则可能更好地利用资源来调用abort方法并吞下来自您正在等待的操作的任何异常(调用stop时必须翻转一下,并在其他等待操作的区域中监视该位)。 这给代码增加了一些复杂性,但是如果您担心资源利用和尽快清理,并且可以使用此选择,那么这就是要走的路。

如果资源利用率不是问题,并且您对更协作的机制感到满意,并且您共享资源,那么使用WithWaitCancellation方法就可以了。 这里的优点是它的代码更简洁,更易于维护。

尽管casperOne的答案是正确的,但对于实现相同目标的WithCancellation (或WithWaitCancellation )扩展方法,存在一种更干净的潜在实现:

static Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken)
{
    return task.IsCompleted
        ? task
        : task.ContinueWith(
            completedTask => completedTask.GetAwaiter().GetResult(),
            cancellationToken,
            TaskContinuationOptions.ExecuteSynchronously,
            TaskScheduler.Default);
}
  • 首先,我们通过检查任务是否已经完成来进行快速路径优化。
  • 然后,我们只需注册到原始任务的延续,然后传递CancellationToken参数。
  • 继续操作在可能的情况下( TaskContinuationOptions.ExecuteSynchronously )同步地提取原始任务的结果(如果有的话,则异常),如果没有,则使用ThreadPool线程( TaskScheduler.Default ),同时观察CancellationToken是否取消。

如果原始任务在CancellationToken之前完成,则返回的任务将存储结果,否则该任务将被取消并在等待时抛出TaskCancelledException

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM