如何从工作线程轮询取消

时间:2013-02-28 17:58:17

标签: c# multithreading cancellation

我有一个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);
    }
}

但是我想这不会因某种原因而起作用,更重要的是我怀疑这样的东西确实存在。如果没有,是否有比我目前使用的更好的方法来处理这种情况?感谢。

3 个答案:

答案 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;
            }
        }
    }