Cách tốt nhất để bắt ngoại lệ trong Tác vụ là gì?


82

Với System.Threading.Tasks.Task<TResult>, tôi phải quản lý các ngoại lệ có thể được ném ra. Tôi đang tìm cách tốt nhất để làm điều đó. Cho đến nay, tôi đã tạo một lớp cơ sở quản lý tất cả các trường hợp ngoại lệ chưa được giải quyết bên trong lệnh gọi của.ContinueWith(...)

Tôi tự hỏi nếu có cách nào tốt hơn để làm điều đó. Hoặc ngay cả khi đó là một cách tốt để làm điều đó.

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
             */

        });        
    }
}

Câu trả lời:


107

Có hai cách bạn có thể thực hiện việc này, tùy thuộc vào phiên bản ngôn ngữ bạn đang sử dụng.

C # 5.0 trở lên

Bạn có thể sử dụng asyncawaittừ khóa để đơn giản hóa rất nhiều việc này cho bạn.

asyncawaitđã được đưa vào ngôn ngữ để đơn giản hóa việc sử dụng Thư viện song song tác vụ , ngăn bạn phải sử dụng ContinueWithvà cho phép bạn tiếp tục lập trình theo cách từ trên xuống.

Do đó, bạn có thể chỉ cần sử dụng a try/catch block để bắt ngoại lệ, như sau:

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

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

Lưu ý rằng phương thức đóng gói ở trên phải sử dụng asynctừ khóa được áp dụng để bạn có thể sử dụng await.

C # 4.0 trở xuống

Bạn có thể xử lý các ngoại lệ bằng cách sử dụng ContinueWithquá tải lấy một giá trị từ kiểu TaskContinuationOptionsliệt kê , như sau:

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

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

Thành OnlyOnFaultedviên của kiểu TaskContinuationOptionsliệt kê chỉ ra rằng phần tiếp theo chỉ nên được thực hiện nếu tác vụ trước đó đã ném một ngoại lệ.

Tất nhiên, bạn có thể có nhiều cuộc gọi đến ContinueWithcùng một tiền đề, xử lý trường hợp không ngoại lệ:

// 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();

1
Làm thế nào bạn biết loại ngoại lệ trong phương thức ẩn danh? Nếu tôi làm t.Exception, IntelliSense wont lộ InnerException, tin nhắn ... vv tính ...
guiomie

4
@guiomie t ngoại lệ.
casperOne

2
ngữ cảnh không được xác định nó là gì?
MonsterMMORPG

@MonsterMMORPG SynchronizationContext, nếu cần.
casperOne

Ty cho câu trả lời tại sao chúng ta cần nó? Ý tôi là trong trường hợp nào? Như thế này đủ chưa ? myTask.ContinueWith (t => ErrorLogger.LogError ("Lỗi xảy ra tại func_CheckWaitingToProcessPages đã bắt đầu tác vụ và lỗi:" + t), TaskContinuationOptions.OnlyOnFaulted);
MonsterMMORPG

5

Bạn có thể tạo một số nhà máy Task tùy chỉnh, sẽ tạo ra các Task có nhúng xử lý xử lý ngoại lệ. Một cái gì đó như thế này:

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
        );
    }
};

Bạn có thể quên việc xử lý ngoại lệ đối với Công việc được sản xuất từ ​​nhà máy này trong mã khách hàng của bạn. Đồng thời, bạn vẫn có thể đợi hoàn thành các Nhiệm vụ như vậy hoặc sử dụng chúng theo kiểu Fire-and-Forget:

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

Nhưng nếu thành thật mà nói, tôi không thực sự chắc chắn tại sao bạn muốn có mã xử lý hoàn thành. Trong mọi trường hợp, quyết định này phụ thuộc vào logic của ứng dụng của bạn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.