假设您不想在
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的博客文章"How do I cancel non-cancelable async operations?"。
这里需要强调的是,这并不实际上取消操作,因为AcceptTcpClientAsync
方法没有一个带有CancellationToken
的重载,所以它无法被取消。
这意味着,如果扩展方法表明取消确实发生了,那么你正在取消对原始Task
回调的等待,而不是取消操作本身。
为此,我将方法从
WithCancellation
重命名为
WithWaitCancellation
,以表明您取消的是
等待而不是实际操作。
从那里开始,在您的代码中使用它很容易:
var tcpListener = new TcpListener(connection);
tcpListener.Start();
var cancellationToken = ...;
try
{
var client = await tcpListener.AcceptTcpClientAsync().
WithWaitCancellation(cancellationToken);
}
catch (AggregateException ae)
{
}
catch (OperationCancelledException oce)
{
}
请注意,如果等待被取消,则必须包装对客户端的调用以捕获抛出的
OperationCanceledException
实例。
我还添加了一个
AggregateException
catch,因为从异步操作中抛出异常时会进行包装(在这种情况下,您应该自行测试)。
这就留下了一个问题,即在有一个像
Stop
方法这样的方法(基本上是任何猛烈拆除一切的方法,无论正在发生什么),哪种方法更好,这当然取决于您的情况。
如果您不共享正在等待的资源(在本例中为
TcpListener
),那么调用 abort 方法并吞噬任何来自等待操作的异常可能会更好地利用资源(当您调用 stop 并在其他等待操作的区域监视该位时,必须翻转一位)。这样会增加代码的复杂性,但如果您关心资源利用率并尽快清理资源,并且可以选择此选项,则应该采用此方法。
如果资源利用不是问题,并且您对更协作的机制感到满意,并且不共享资源,则使用 WithWaitCancellation 方法是可以的。优点在于它是更干净和更易于维护的代码。