C# 在 Task 中捕获异常的最佳方法是什么?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12980712/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 01:11:54  来源:igfitidea点击:

What is the best way to catch exception in Task?

c#.nettask-parallel-library

提问by JiBéDoublevé

With System.Threading.Tasks.Task<TResult>, I have to manage the exceptions that could be thrown. I'm looking for the best way to do that. So far, I've created a base class that manages all the uncaught exceptions inside the call of .ContinueWith(...)

使用System.Threading.Tasks.Task<TResult>,我必须管理可能抛出的异常。我正在寻找最好的方法来做到这一点。到目前为止,我已经创建了一个基类来管理调用中的所有未捕获的异常.ContinueWith(...)

I'm wondering if there's a better way do do that. Or even if it is a good way to do that.

我想知道是否有更好的方法来做到这一点。或者即使这是一个很好的方法。

public class BaseClass
{
    protected void ExecuteIfTaskIsNotFaulted<T>(Task<T> e, Action action)
    {
        if (!e.IsFaulted) { action(); }
        else
        {
            Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() =>
            {
                /* I display a window explaining the error in the GUI 
                 * and I log the error.
                 */
                this.Handle.Error(e.Exception);
            }));            
        }
    }
}   

public class ChildClass : BaseClass
{
    public void DoItInAThread()
    {
        var context = TaskScheduler.FromCurrentSynchronizationContext();
        Task.Factory.StartNew<StateObject>(() => this.Action())
                    .ContinueWith(e => this.ContinuedAction(e), context);
    }

    private void ContinuedAction(Task<StateObject> e)
    {
        this.ExecuteIfTaskIsNotFaulted(e, () =>
        {
            /* The action to execute 
             * I do stuff with e.Result
             */

        });        
    }
}

采纳答案by casperOne

There are two ways you can do this, dependent on the version of the language you are using.

有两种方法可以执行此操作,具体取决于您使用的语言版本。

C# 5.0 and above

C# 5.0 及以上

You can use the asyncand awaitkeywords to simplify a great deal of this for you.

您可以使用asyncawait关键字为您简化大量工作。

asyncand awaitwere introduced into the language to simplify using the Task Parallel Library, preventing you from having to use ContinueWithand allowing you to continue to program in a top-down manner.

asyncawait引入了语言使用简化任务并行库,阻止您不必使用ContinueWith,并允许你继续计划在自上而下的方式。

Because of this, you can simply use a try/catchblock to catch the exception, like so:

因此,您可以简单地使用try/catch块来捕获异常,如下所示:

try
{
    // Start the task.
    var task = Task.Factory.StartNew<StateObject>(() => { /* action */ });

    // Await the task.
    await task;
}
catch (Exception e)
{
    // Perform cleanup here.
}

Note that the method encapsulating the above mustuse have the asynckeyword applied so you can use await.

请注意,封装上述内容的方法必须使用async应用关键字,以便您可以使用await.

C# 4.0 and below

C# 4.0 及以下

You can handle exceptions using the ContinueWithoverloadthat takes a value from the TaskContinuationOptionsenumeration, like so:

您可以使用从enumeration中获取值的ContinueWith重载来处理异常,如下所示:TaskContinuationOptions

// Get the task.
var task = Task.Factory.StartNew<StateObject>(() => { /* action */ });

// For error handling.
task.ContinueWith(t => { /* error handling */ }, context,
    TaskContinuationOptions.OnlyOnFaulted);

The OnlyOnFaultedmember of the TaskContinuationOptionsenumeration indicates that the continuation should onlybe executed if the antecedent task threw an exception.

OnlyOnFaulted该成员TaskContinuationOptions枚举指示应继续当先行任务抛出异常执行。

Of course, you can have more than one call to ContinueWithoff the same antecedent, handling the non-exceptional case:

当然,您可以ContinueWith对同一个前提进行多次调用,以处理非异常情况:

// Get the task.
var task = new Task<StateObject>(() => { /* action */ });

// For error handling.
task.ContinueWith(t => { /* error handling */ }, context, 
    TaskContinuationOptions.OnlyOnFaulted);

// If it succeeded.
task.ContinueWith(t => { /* on success */ }, context,
    TaskContinuationOptions.OnlyOnRanToCompletion);

// Run task.
task.Start();

回答by ZarathustrA

You can create some custom Task factory, which will produce Tasks with exception handling processing embedded. Something like this:

您可以创建一些自定义任务工厂,这将生成嵌入了异常处理处理的任务。像这样的东西:

using System;
using System.Threading.Tasks;

class FaFTaskFactory
{
    public static Task StartNew(Action action)
    {
        return Task.Factory.StartNew(action).ContinueWith(
            c =>
            {
                AggregateException exception = c.Exception;

                // Your Exception Handling Code
            },
            TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously
        ).ContinueWith(
            c =>
            {
                // Your task accomplishing Code
            },
            TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously
        );
    }

    public static Task StartNew(Action action, Action<Task> exception_handler, Action<Task> completion_handler)
    {
        return Task.Factory.StartNew(action).ContinueWith(
            exception_handler,
            TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously
        ).ContinueWith(
            completion_handler,
            TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously
        );
    }
};

You can forget about exceptions processing for Tasks produced from this factory in your client code. In the same time you still can wait finishing of such Tasks or use them in Fire-and-Forget style:

您可以忘记客户端代码中从该工厂生成的任务的异常处理。同时,您仍然可以等待此类任务完成或以“即发即忘”的方式使用它们:

var task1 = FaFTaskFactory.StartNew( () => { throw new NullReferenceException(); } );
var task2 = FaFTaskFactory.StartNew( () => { throw new NullReferenceException(); },
                                      c => {    Console.WriteLine("Exception!"); },
                                      c => {    Console.WriteLine("Success!"  ); } );

task1.Wait(); // You can omit this
task2.Wait(); // You can omit this

But if be honest I'm not really sure why you want to have completion handling code. In any case this decision depends on the logic of your application.

但说实话,我不太确定你为什么想要完成处理代码。无论如何,此决定取决于您的应用程序的逻辑。