我有一个UI,它产生一个后台工作线程,它执行一个复杂的任务和子任务树,大约需要一分钟才能完成。
要求是后台工作人员任务必须能够在开始后被取消。
目前我的解决方案很天真并且使代码变得混乱。当在UI中按下取消按钮时,设置取消令牌。工作线程定期(在任务之间)轮询此令牌,如果已设置,则退出:
void ThreadWorkerHandler(CancelToken cancelToken)
{
DoTask1(cancelToken);
if (cancelToken.IsSet)
return;
DoTask2(cancelToken);
if (cancelToken.IsSet)
return;
DoTask3(cancelToken);
if (cancelToken.IsSet)
return;
DoTask4(cancelToken);
}
void DoTask2(CancelToken cancelToken)
{
DoSubTask2a();
if (cancelToken.IsSet)
return;
DoSubTask2b();
if (cancelToken.IsSet)
return;
DoSubTask2c();
if (cancelToken.IsSet)
return;
}
有更好的解决方案吗?我正在寻找类似于SoLongAs语句的东西,它会自动对检查进行自动检查并在条件满足时引发内部异常,这将在循环结束时内部捕获,例如:
void ThreadWorkerHandler(CancelToken cancelToken)
{
SoLongAs (canelToken.IsSet == false)
{
DoTask1(cancelToken);
DoTask2(cancelToken);
DoTask3(cancelToken);
DoTask4(cancelToken);
}
}
但是我想这不会因某种原因而起作用,更重要的是我怀疑这样的东西确实存在。如果没有,是否有比我目前使用的更好的方法来处理这种情况?感谢。
答案 0 :(得分:2)
如果您有一组代表您工作的代表,您可以获得与您的代码段非常接近的内容。它的位比你想要的语法有更多的开销,但关键是它是常量开销,而不是每行开销。
List<Action> actions = new List<Action>()
{
()=> DoTask1(cancelToken),
()=> DoTask2(cancelToken),
()=> DoTask3(cancelToken),
()=> DoTask4(cancelToken),
};
foreach(var action in actions)
{
if (!cancelToken.IsSet)
action();
}
答案 1 :(得分:1)
您可以使用CancellationToken.ThrowIfCancellationRequested()
。如果设置了令牌,这将抛出异常。
另请考虑使用TPL
Tasks
。所有子任务可以使用相同的CancellationToken
一个接一个地链接,这将简化您的代码,因为TPL
框架会在调用continuation之前检查Token
状态。
您的代码如下所示:
Task.Factory.StartNew(DoTask1, cancelationToken)
.ContinueWith(t => DoTask2(), cancelationToken)
.ContinueWith(t => DoTask3(), cancelationToken)
.ContinueWith(t => DoTask4(), cancelationToken)
注意此解决方案假设DoTask<i>
不会抛出除OperationCanceledException
之外的其他例外。
Note2 您无需在Tasks / subTasks正文中调用ThrowIfCancellationRequested()
。 TPL
会在调用任何延续之前自动检查令牌状态。但是 可以 使用此方法来中断任务/子任务的执行。
答案 2 :(得分:0)
Servy的想法非常好。我只是偷了它(给予他所有的信任!)并演示了如何将它与List<Action>
的扩展方法一起使用。我完全理解任何认为这“太可爱”的人,但我觉得它有一定的优雅。
这是一个展示如何使用扩展方法的exerpt。根据Servy的想法,扩展程序会获取一系列Action代理并依次运行每个代理,直到完成或取消。
private static bool test(CancellationToken cancelToken)
{
return new List<Action>
{
doTask1,
doTask2,
doTask3,
doTask4,
() => Console.WriteLine("Press a key to exit.")
}
.Run(cancelToken);
}
以下是整个样本:
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication2
{
internal class Program
{
private static void Main(string[] args)
{
CancellationTokenSource cancelSource = new CancellationTokenSource();
Console.WriteLine("Press any key to interrupt the work.");
var work = Task<bool>.Factory.StartNew(() => test(cancelSource.Token));
Console.ReadKey();
cancelSource.Cancel();
Console.WriteLine(work.Result ? "Completed." : "Interrupted.");
}
private static bool test(CancellationToken cancelToken)
{
return new List<Action>
{
doTask1,
doTask2,
doTask3,
doTask4,
() => Console.WriteLine("Press a key to exit.")
}
.Run(cancelToken);
}
private static void doTask1()
{
Console.WriteLine("Task 1 Working...");
Thread.Sleep(1000);
Console.WriteLine("...did some work.");
}
private static void doTask2()
{
Console.WriteLine("Task 2 Working...");
Thread.Sleep(1000);
Console.WriteLine("...did some work.");
}
private static void doTask3()
{
Console.WriteLine("Task 3 Working...");
Thread.Sleep(1000);
Console.WriteLine("...did some work.");
}
private static void doTask4()
{
Console.WriteLine("Task 4 Working...");
Thread.Sleep(1000);
Console.WriteLine("...did some work.");
}
}
public static class EnumerableActionExt
{
public static bool Run(this IEnumerable<Action> actions, CancellationToken cancelToken)
{
foreach (var action in actions)
{
if (!cancelToken.IsCancellationRequested)
{
action();
}
else
{
return false;
}
}
return true;
}
}
}