【问题标题】:How to break the chain of Task when an exception occurs?发生异常时如何中断Task链?
【发布时间】:2017-01-10 09:37:33
【问题描述】:

我创造了作品链。他们将在我的附加线程中工作。为此,我使用Task。另外,如果发生任何异常,我想中断链的工作并将其抛出到调用线程中。但我看到我的链没有断,act2act3 也完成了。

我该如何解决?

using System;
using System.Threading.Tasks;

namespace Bushman.Sandbox.Threads {
    class Program {
        static void Main(string[] args) {
            Console.Title = "Custom thread";

            try {
                // First work
                Action act1 = () => {
                    for (int i = 0; i < 5; i++) {

                        // I throw the exeption here
                        if (i == 3) throw new Exception("Oops!!!");

                        Console.WriteLine("Do first work");
                    }
                };

                // Second work
                Action act2 = () => {
                    for (int i = 0; i < 5; i++)
                        Console.WriteLine("  Do second work");
                };

                // Third work
                Func<int> act3 = () => {
                    for (int i = 0; i < 5; i++)
                        Console.WriteLine("    Do third work");
                    return 12345;
                };

                Task task = new Task(act1);

                // Build the chain of the works
                var awaiter = task.ContinueWith(_ => act2(),
                    TaskContinuationOptions.ExecuteSynchronously)
                    .ContinueWith(_ => act3(),
                    TaskContinuationOptions.ExecuteSynchronously)
                    .GetAwaiter();

                Console.WriteLine("Work started...");

                // launch the chain
                task.Start();

                // Here I get some result
                int result = awaiter.GetResult(); // 12345

                if (task.IsCanceled || task.IsFaulted) {
                    throw task.Exception.InnerException;
                }

                Console.WriteLine("The result: {0}",
                    result.ToString());
            }
            catch (Exception ex) {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine(ex.Message);
                Console.ResetColor();
            }

            Console.WriteLine("Press any key for exit...");
            Console.ReadKey();
        }
    }
}

【问题讨论】:

  • 你为什么不直接使用async/await?您也不需要Task.Start 或直接访问等待者。此外,如果您想使用ContinueWith,您需要访问任务以检查异常,例如 - ContinueWith(t =&gt; if (t.Faulted) {..} else act3();)
  • 我通过一本书学会了使用线程\任务。我还没有读到async/await
  • 如果您不想使用async/await,我建议您查看有关如何正确使用的文档示例,例如 ContinueWith 和 TasFactory.StartNew 或 Task.Run。还要检查 Task.Result 和 Task.Wait() 做什么 - 如果任务出错,它们 do 会抛出。您的代码过于复杂,并试图公开它不需要的实现内部

标签: c# .net multithreading task multitasking


【解决方案1】:

您必须使用 NotOnFaulted 任务继续选项。

由于TaskContinuationOptions 被Flags 属性修饰,您可以将NotFaulted 与其他选项结合使用。

 var awaiter = task.ContinueWith(_ => act2(),
                TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.NotOnFaulted)
                .ContinueWith(_ => act3(),
                TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.NotOnFaulted)
                .GetAwaiter();

即使您使用 async/await 关键字,这种方法仍然有效(但您摆脱了 GetAwaiter 调用)

【讨论】:

  • 谢谢。在这种情况下,act2 没有启动,但 act3 仍然启动。
  • 哦...如果我将TaskContinuationOptions.NotOnCanceledTaskContinuationOptions.NotOnFaulted 一起添加,那么它会像我预期的那样工作。谢谢!
  • 试试 AttachedToParent 标志。
  • 请注意,如果您使用 ExecuteSynchronously 标志,您并没有真正使用任务的真正力量。
【解决方案2】:

代码试图以非常规的方式使用任务,几乎就像它们是线程一样。它们不是 - 任务是一项将被安排在线程池线程上运行的作业,而不是线程本身。调用Task.Start 不会执行任何操作,它会安排其委托在线程上运行。这就是为什么从不使用构造函数创建任务的原因。

启动和协调任务最简单的方法是使用Task.Run和async/await,例如:

public static async Task<int> MyMethodAsync()
{
    try
    {
        await Task.Run(()=>act1());
        await Task.Run(()=>act2());
        var result=await Task.Run(()=>act3());
        return result;
    }
    catch (Exception exc)
    {
           //Do something
    }
}

您不能在控制台应用程序的 Main 函数中使用 async/await,因此您必须按以下方式调用该方法:

var result=MyMethodAsync().Result;

在任务上调用 .Wait().Result 会重新引发其中引发的任何异常。

如果没有async/await,您需要使用ContinueWith 并实际检查上一个任务的结果。如果你只是想停止处理,你可以通过 TaskContinuationOptions.NotOnFaulted :

var result = Task.Run(()=>act1())
                 .ContinueWith( t1=>act2(),TaskContinuationOptions.NotOnFaulted)
                 .ContinueWith( t2=>act3(),TaskContinuationOptions.NotOnFaulted)
                 .Result;

您不需要显式访问等待者。对.Result 的最终调用将返回整数结果,或者如果先前的任务之一出错则抛出AggregateException

【讨论】:

  • 使用 ContinueWith 方法是“常规的”。我同意你的观点,async/await 更简洁,更易于维护,但使用 Start/GetAwaiter/GetResults 并没有错。最后,async/await 只是这些方法的语法糖。
  • @MatteoMarciano-MSCP 这些方法仅在没有任务的 WinRT 中是必需的。没有其他运行时需要它们。至于为什么使用它们是错误的,代码的复杂性是显而易见的。代码最终变得更加复杂,没有任何好处。唯一能找到Task.Start 的地方是在 SO 问题中
  • 在某些情况下仍然使用并且仍然需要继续。关于 WinRT 不支持任务,这并不完全正确。也许你在谈论 WinRT 组件。
  • 我没有说不使用延续。我说没有使用等待者
猜你喜欢
  • 1970-01-01
  • 2012-09-10
  • 2014-05-15
  • 1970-01-01
  • 2010-11-14
  • 1970-01-01
  • 2012-01-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多