.NET TPL擁有非常大的靈活性,你會發現同一個操作會有許多不同的實現方式。正如標題所講,我們來看這樣一個簡答的操作:取消Task的執行,然後在當前SynchronizationContext中執行代碼。當然一切操作都必須是非同步,因此不能使用Task.Wait這樣的方法。
我想到的實現方法有三種。
首先是準備工作,先寫一個方法用來執行一個可以取消的Task,之後的具體實現代碼就直接調用這個方法:
//執行一個可以取消的Task
static Task NewCancellableTask(CancellationToken token)
{
return Task.Run(() =>
{
while (true)
{
System.Threading.Thread.Sleep(1000);
token.ThrowIfCancellationRequested();
}
});
}
接下來看三種具體實現。
目錄
- 方法一:使用await
- 方法二:使用ContinueWith和TaskScheduler
- 方法三:使用CancellationToken.Register方法
返回目錄
方法一:使用await
這必須是首選,C# 5.0的await隱藏了許多TPL中的邏輯,以至於可以使非同步執行的代碼從外表上看起來和同步執行沒太大區別。如下代碼:
var cts = new CancellationTokenSource();
//執行取消操作
cts.CancelAfter(1000);
try
{
await NewCancellableTask(cts.Token);
}
catch (OperationCanceledException)
{
//收集OperationCanceledException
MessageBox.Show("操作取消");
}
運行幾秒鐘後,會顯示“操作取消”對話方塊。
返回目錄
方法二:使用ContinueWith和TaskScheduler
第二種方法我使用Task.ContinueWith,並且使用:
TaskContinuationOptions.OnlyOnCanceled
來確保只有在Task被取消後,後續操作才會被執行。
由於沒有用await,因此SynchronizationContext的執行需要我們自己來做,方法就是使用TaskScheduler.FromCurrentSynchronizationContext方法來返回一個從當前SynchronizationContext建立的TaskScheduler。
Task.ContinueWith有諸多重載,我們使用這一個:
Task<TResult> ContinueWith<TResult>(Func<Task, TResult> continuationFunction, CancellationToken cancellationToken, TaskContinuationOptions continuationOptions, TaskScheduler scheduler);
最終代碼:
var cts = new CancellationTokenSource();
//執行取消操作
cts.CancelAfter(1000);
//ContinueWith
NewCancellableTask(cts.Token).ContinueWith(
t => MessageBox.Show("操作取消"),
CancellationToken.None,
TaskContinuationOptions.OnlyOnCanceled,
TaskScheduler.FromCurrentSynchronizationContext());
(ContinueWith的Task事實上返回一個Task<MessageBoxResult>)
同樣,運行成功。
返回目錄
方法三:使用CancellationToken.Register方法
CancellationToken類型有一個Register方法可以傳入一個委託,當CancellationToken被取消後這個委託會被調用。更不可思議的是,它還有一個useSynchronizationContext參數來指定是否回調方法被執行在當前SynchronizationContext上,非常給力。當然Register方法還可以傳遞一個額外參數,本例不需要使用。
:CancellationToken.Register方法的參數:
那麼使用這種方法的實現代碼:
var cts = new CancellationTokenSource();
//註冊操作
cts.Token.Register(() => MessageBox.Show("操作取消"), true);
//執行取消操作
cts.CancelAfter(1000);
//直接執行Task,不需要其他動作。
NewCancellableTask(cts.Token);
這種方法比較少見,不過看起來還不錯。