取消以下内容的正确方法是什么?
var tcpListener = new TcpListener(connection);
tcpListener.Start();
var client = await tcpListener.AcceptTcpClientAsync();
简单地调用tcpListener.Stop()
似乎会产生ObjectDisposedException
而AcceptTcpClientAsync
方法不接受CancellationToken
结构。
我完全错过了一些明显的东西吗?
答案 0 :(得分:22)
假设您不想拨打Stop
method上的TcpListener
class,这里没有完美的解决方案。
如果您在某个时间范围内未完成操作时收到通知,但允许原始操作完成,那么您可以创建一个扩展方法,如下所示:
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's blog post "How do I cancel non-cancelable async operations?"。
这里需要注意的是,这实际上并没有取消操作,因为AcceptTcpClientAsync
method的重载没有CancellationToken
,它不是< em>能被取消。
这意味着如果扩展方法指示取消发生,则取消原始Task
回复的等待,不取消操作本身。
为此,我们已将该方法从WithCancellation
重命名为WithWaitCancellation
,以表示您正在取消等待,而不是实际操作。< / p>
从那里开始,它很容易在您的代码中使用:
// 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
catch,因为从异步操作中抛出异常(在这种情况下你应该自己测试)。
在面对像Stop
method这样的方法时,哪种方法是更好的方法的问题(基本上,无论发生了什么,都会猛烈地撕下所有东西),当然,取决于你的情况。
如果你没有共享你正在等待的资源(在这种情况下是TcpListener
),那么调用abort方法并吞下任何异常可能会更好地利用资源来自您正在等待的操作(当您调用停止时,您必须稍微翻转并在您正在等待操作的其他区域中监视该位)。这增加了代码的复杂性,但如果您担心资源利用率和尽快清理,并且您可以选择此选项,那么这就是您的选择。
如果资源利用不是一个问题,并且您对更合作的机制感到满意,并且您不共享资源,那么使用{{1方法很好。这里的优点是代码更清晰,更易于维护。
答案 1 :(得分:11)
虽然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
线程,同时观察{ {1}}取消。如果原始任务在取消CancellationToken
之前完成,则返回的任务将存储结果,否则任务将被取消,并在等待时抛出CancellationToken
。