我们的应用程序使用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