取消不接受 CancellationToken 的异步操作的正确方法是什么? [英] What is the correct way to cancel an async operation that doesn't accept a CancellationToken?

查看:26
本文介绍了取消不接受 CancellationToken 的异步操作的正确方法是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

What is the correct way to cancel the following?

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

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

Simply calling tcpListener.Stop() seems to result in an ObjectDisposedException and the AcceptTcpClientAsync method doesn't accept a CancellationToken structure.

我是否完全遗漏了一些明显的东西?

Am I totally missing something obvious?

推荐答案

假设您不想调用 Stop 方法TcpListener,这里没有完美的解决方案.

Assuming that you don't want to call the Stop method on the TcpListener class, there's no perfect solution here.

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

If you're alright with being notified when the operation doesn't complete within a certain time frame, but allowing the original operation to complete, then you can create an extension method, like so:

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,它不是能够取消了.

The caveat here bears repeating, this doesn't actually cancel the operation, because there is not an overload of the AcceptTcpClientAsync method that takes a CancellationToken, it's not able to be cancelled.

这意味着如果扩展方法指示取消did发生,则您正在取消对原始Task取消操作本身.

That means that if the extension method indicates that a cancellation did happen, you are cancelling the wait on the callback of the original Task, not cancelling the operation itself.

为此,这就是为什么我将方法从 WithCancellation 重命名为 WithWaitCancellation 以表明您正在取消 wait,不是实际行动.

To that end, that is why I've renamed the method from WithCancellation to WithWaitCancellation to indicate that you are cancelling the wait, not the actual action.

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

From there, it's easy to use in your code:

// 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 等待被取消时抛出的实例.

Note that you'll have to wrap the call for your client to capture the OperationCanceledException instance thrown if the wait is cancelled.

我还抛出了一个 AggregateException 捕获,因为从异步操作抛出异常时会被包装(在这种情况下,您应该自己测试).

I've also thrown in an AggregateException catch as exceptions are wrapped when thrown from asynchronous operations (you should test for yourself in this case).

面对像 Stop 方法(基本上,任何猛烈地破坏一切的东西,不管发生了什么),当然,这取决于你的情况.

That leaves the question of which approach is a better approach in the face of having a method like the Stop method (basically, anything which violently tears everything down, regardless of what is going on), which of course, depends on your circumstances.

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

If you are not sharing the resource that you're waiting on (in this case, the TcpListener), then it would probably be a better use of resources to call the abort method and swallow any exceptions that come from operations you're waiting on (you'll have to flip a bit when you call stop and monitor that bit in the other areas you're waiting on an operation). This adds some complexity to the code but if you're concerned about resource utilization and cleaning up as soon as possible, and this choice is available to you, then this is the way to go.

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

If resource utilization is not an issue and you're comfortable with a more cooperative mechanism, and you're not sharing the resource, then using the WithWaitCancellation method is fine. The pros here are that it's cleaner code, and easier to maintain.

这篇关于取消不接受 CancellationToken 的异步操作的正确方法是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆