[英]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
重命名為WithCancellation
的WithWaitCancellation
以指示您要取消的是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.