0

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

但我想由于某种原因这行不通,更重要的是我怀疑这样的事情是否真的存在。如果没有,有没有比我目前使用的更好的方法来处理这种情况?谢谢。

4

3 回答 3

2

如果您有一组代表您的工作的代表,您可以获得看起来非常接近您的代码片段的东西。它比您的意图语法有更多的开销,但关键是它是一个恒定的开销,而不是每行的开销。

List<Action> actions = new List<Action>()
{
    ()=> DoTask1(cancelToken),
    ()=> DoTask2(cancelToken),
    ()=> DoTask3(cancelToken),
    ()=> DoTask4(cancelToken),
};

foreach(var action in actions)
{
    if (!cancelToken.IsSet)
        action();
}
于 2013-02-28T18:02:17.070 回答
1

您可以使用CancellationToken.ThrowIfCancellationRequested(). 如果设置了令牌,这将引发异常。

也考虑使用TPL Tasks. 所有子任务都可以一个接一个地用 same 链接起来CancellationToken,这将简化你的代码,因为框架会在调用延续之前TPL检查状态。Token

您的代码如下所示:

Task.Factory.StartNew(DoTask1, cancelationToken)
            .ContinueWith(t => DoTask2(), cancelationToken)
            .ContinueWith(t => DoTask3(), cancelationToken)
            .ContinueWith(t => DoTask4(), cancelationToken)

请注意此解决方案,假设DoTask<i>不会引发除OperationCanceledException.

注意 2您不必ThrowIfCancellationRequested()在 Tasks/subTasks 正文中调用。TPL将在调用任何延续之前自动检查令牌状态。但是您可以使用此方法中断任务/子任务的执行。

于 2013-02-28T18:06:05.090 回答
0

Servy的想法非常好。我只是在偷它(归功于他!)并演示如何将它与List<Action>. 我会完全理解任何认为这“太可爱”的人,但我认为它具有一定的优雅。

这是一段展示如何使用扩展方法的摘录。根据 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;
            }
        }
    }
于 2013-02-28T18:44:16.127 回答