我們的應用程序使用TPL序列化(可能)長時間運行的工作單元。工作(任務)的創建是由用戶驅動的,可能隨時取消。爲了有一個響應式用戶界面,如果不再需要當前的工作,我們想放棄我們正在做的事情,並立即開始一項不同的工作。在TPL中取消長時間運行的任務
任務排隊等候是這樣的:
private Task workQueue;
private void DoWorkAsync
(Action<WorkCompletedEventArgs> callback, CancellationToken token)
{
if (workQueue == null)
{
workQueue = Task.Factory.StartWork
(() => DoWork(callback, token), token);
}
else
{
workQueue.ContinueWork(t => DoWork(callback, token), token);
}
}
的DoWork
方法中包含一個長期運行的通話,所以它不是那麼簡單,經常檢查token.IsCancellationRequested
狀態與脫困如果/當檢測到取消。長時間運行的工作將阻止任務繼續,直至完成,即使任務被取消。
我想出了兩個解決此問題的示例方法,但我不相信這兩個方法都是正確的。我創建了簡單的控制檯應用程序來演示它們如何工
需要注意的重要一點是繼續在原始任務完成之前觸發。
嘗試#1:一個內部任務
static void Main(string[] args)
{
CancellationTokenSource cts = new CancellationTokenSource();
var token = cts.Token;
token.Register(() => Console.WriteLine("Token cancelled"));
// Initial work
var t = Task.Factory.StartNew(() =>
{
Console.WriteLine("Doing work");
// Wrap the long running work in a task, and then wait for it to complete
// or the token to be cancelled.
var innerT = Task.Factory.StartNew(() => Thread.Sleep(3000), token);
innerT.Wait(token);
token.ThrowIfCancellationRequested();
Console.WriteLine("Completed.");
}
, token);
// Second chunk of work which, in the real world, would be identical to the
// first chunk of work.
t.ContinueWith((lastTask) =>
{
Console.WriteLine("Continuation started");
});
// Give the user 3s to cancel the first batch of work
Console.ReadKey();
if (t.Status == TaskStatus.Running)
{
Console.WriteLine("Cancel requested");
cts.Cancel();
Console.ReadKey();
}
}
這工作,但 「innerT」 任務感到非常kludgey給我。它還有一個缺點,就是迫使我重新構造以這種方式排隊工作的代碼的所有部分,因爲必須在新任務中包含所有長時間運行的調用。
嘗試#2:TaskCompletionSource修修補補
static void Main(string[] args)
{ var tcs = new TaskCompletionSource<object>();
//Wire up the token's cancellation to trigger the TaskCompletionSource's cancellation
CancellationTokenSource cts = new CancellationTokenSource();
var token = cts.Token;
token.Register(() =>
{ Console.WriteLine("Token cancelled");
tcs.SetCanceled();
});
var innerT = Task.Factory.StartNew(() =>
{
Console.WriteLine("Doing work");
Thread.Sleep(3000);
Console.WriteLine("Completed.");
// When the work has complete, set the TaskCompletionSource so that the
// continuation will fire.
tcs.SetResult(null);
});
// Second chunk of work which, in the real world, would be identical to the
// first chunk of work.
// Note that we continue when the TaskCompletionSource's task finishes,
// not the above innerT task.
tcs.Task.ContinueWith((lastTask) =>
{
Console.WriteLine("Continuation started");
});
// Give the user 3s to cancel the first batch of work
Console.ReadKey();
if (innerT.Status == TaskStatus.Running)
{
Console.WriteLine("Cancel requested");
cts.Cancel();
Console.ReadKey();
}
}
同樣,這工作,但現在我有兩個問題:
一)感覺就像我濫用TaskCompletionSource通過從來沒有使用它的結果,當我完成我的工作時就設置爲空。
b)爲了正確連接繼續,我需要保留前一個工作單元的獨特TaskCompletionSource的句柄,而不是爲其創建的任務。這在技術上是可行的,但又感到笨重和奇怪。
下一步該怎麼辦?
重申,我的問題是:這些方法中的哪一種都是解決這個問題的「正確」方法,還是有更正確/優雅的解決方案,可以讓我過早地中止長時間運行的任務並立即開始延續?我的偏好是低影響的解決方案,但如果這是正確的做法,我願意承擔一些巨大的重構。
或者,TPL甚至是工作的正確工具,還是我錯過了更好的任務排隊機制。我的目標框架是.NET 4.0。
我也問這個問題在這裏:http://social.msdn.microsoft.com/Forums/en/parallelextensions/thread/d0bcb415-fb1e-42e4-90f8-c43a088537fb – 2011-01-21 13:58:03