Một giải pháp tốt để chờ đợi trong thử / bắt / cuối cùng?


93

Tôi cần gọi một asyncphương thức trong một catchkhối trước khi ném lại ngoại lệ (với dấu vết ngăn xếp của nó) như sau:

try
{
    // Do something
}
catch
{
    // <- Clean things here with async methods
    throw;
}

Nhưng tiếc là bạn không thể sử dụng awaittrong một catchhoặc finallykhối. Tôi biết được đó là bởi vì trình biên dịch không có bất kỳ cách nào để quay lại catchkhối để thực thi những gì sau awaitlệnh của bạn hoặc những thứ tương tự ...

Tôi đã cố gắng sử dụng Task.Wait()để thay thế awaitvà tôi đã gặp bế tắc. Tôi đã tìm kiếm trên Web cách tôi có thể tránh điều này và tìm thấy trang web này .

Vì tôi không thể thay đổi các asyncphương thức cũng như không biết chúng có sử dụng hay không ConfigureAwait(false), tôi đã tạo các phương thức này lấy một phương thức Func<Task>bắt đầu một phương thức không đồng bộ khi chúng ta đang ở trên một luồng khác (để tránh bế tắc) và chờ hoàn thành:

public static void AwaitTaskSync(Func<Task> action)
{
    Task.Run(async () => await action().ConfigureAwait(false)).Wait();
}

public static TResult AwaitTaskSync<TResult>(Func<Task<TResult>> action)
{
    return Task.Run(async () => await action().ConfigureAwait(false)).Result;
}

public static void AwaitSync(Func<IAsyncAction> action)
{
    AwaitTaskSync(() => action().AsTask());
}

public static TResult AwaitSync<TResult>(Func<IAsyncOperation<TResult>> action)
{
    return AwaitTaskSync(() => action().AsTask());
}

Vì vậy, câu hỏi của tôi là: Bạn có nghĩ rằng mã này là ổn không?

Tất nhiên, nếu bạn có một số cải tiến hoặc biết cách tiếp cận tốt hơn, tôi sẽ lắng nghe! :)


2
Sử dụng awaittrong một khối catch được thực sự cho phép từ C # 6.0 (xem câu trả lời của tôi dưới đây)
Adi Lester

3
Thông báo lỗi C # 5.0 liên quan : CS1985 : Không thể đợi trong phần nội dung của mệnh đề bắt. CS1984 : Không thể chờ đợi trong nội dung của mệnh đề cuối cùng.
DavidRR

Câu trả lời:


173

Bạn có thể di chuyển logic ra ngoài catchkhối và ném lại ngoại lệ sau, nếu cần, bằng cách sử dụng ExceptionDispatchInfo.

static async Task f()
{
    ExceptionDispatchInfo capturedException = null;
    try
    {
        await TaskThatFails();
    }
    catch (MyException ex)
    {
        capturedException = ExceptionDispatchInfo.Capture(ex);
    }

    if (capturedException != null)
    {
        await ExceptionHandler();

        capturedException.Throw();
    }
}

Bằng cách này, khi người gọi kiểm tra thuộc tính của ngoại lệ StackTrace, nó vẫn ghi lại vị trí bên trong TaskThatFailsnó đã được ném.


1
Lợi ích của việc giữ lại ExceptionDispatchInfothay vì Exception(như trong câu trả lời của Stephen Cleary) là gì?
Varvara Kalinina

2
Tôi có thể đoán rằng nếu bạn quyết định ném lại Exception, bạn sẽ mất tất cả những StackTracegì trước đó ?
Varvara Kalinina

1
@VarvaraKalinina Chính xác.

54

Bạn nên biết rằng kể từ C # 6.0, nó có thể sử dụng awaittrong catchfinallykhối, vì vậy trên thực tế bạn có thể làm điều này:

try
{
    // Do something
}
catch (Exception ex)
{
    await DoCleanupAsync();
    throw;
}

Các tính năng mới của C # 6.0, bao gồm cả tính năng tôi vừa đề cập được liệt kê ở đây hoặc dưới dạng video ở đây .


Hỗ trợ chờ đợi các khối bắt / cuối cùng trong C # 6.0 cũng được chỉ ra trên Wikipedia.
DavidRR

4
@DavidRR. Wikipedia không có thẩm quyền. Nó chỉ là một trang web khác trong số hàng triệu trang web liên quan đến điều này.
user34660 Ngày

Trong khi điều này hợp lệ cho C # 6, câu hỏi đã được gắn thẻ C # 5 ngay từ đầu. Điều này khiến tôi tự hỏi liệu có câu trả lời này ở đây là khó hiểu hay chúng ta chỉ nên xóa thẻ phiên bản cụ thể trong những trường hợp này.
julealgon

16

Nếu bạn cần sử dụng các asynctrình xử lý lỗi, tôi khuyên bạn nên dùng những thứ như sau:

Exception exception = null;
try
{
  ...
}
catch (Exception ex)
{
  exception = ex;
}

if (exception != null)
{
  ...
}

Vấn đề với việc chặn đồng bộ trên asyncmã (bất kể nó đang chạy trên chuỗi nào) là bạn đang chặn đồng bộ. Trong hầu hết các tình huống, tốt hơn là sử dụngawait .

Cập nhật: Vì bạn cần bắn lại, bạn có thể sử dụng ExceptionDispatchInfo.


1
Cảm ơn bạn nhưng tiếc là tôi đã biết phương pháp này rồi. Đó là những gì tôi làm bình thường, nhưng tôi không thể làm điều này ở đây. Nếu tôi chỉ sử dụng throw exception;trong ifcâu lệnh, dấu vết ngăn xếp sẽ bị mất.
user2397050

3

Chúng tôi đã trích xuất câu trả lời tuyệt vời của hvd cho lớp tiện ích có thể tái sử dụng sau trong dự án của chúng tôi:

public static class TryWithAwaitInCatch
{
    public static async Task ExecuteAndHandleErrorAsync(Func<Task> actionAsync,
        Func<Exception, Task<bool>> errorHandlerAsync)
    {
        ExceptionDispatchInfo capturedException = null;
        try
        {
            await actionAsync().ConfigureAwait(false);
        }
        catch (Exception ex)
        {
            capturedException = ExceptionDispatchInfo.Capture(ex);
        }

        if (capturedException != null)
        {
            bool needsThrow = await errorHandlerAsync(capturedException.SourceException).ConfigureAwait(false);
            if (needsThrow)
            {
                capturedException.Throw();
            }
        }
    }
}

Người ta sẽ sử dụng nó như sau:

    public async Task OnDoSomething()
    {
        await TryWithAwaitInCatch.ExecuteAndHandleErrorAsync(
            async () => await DoSomethingAsync(),
            async (ex) => { await ShowMessageAsync("Error: " + ex.Message); return false; }
        );
    }

Vui lòng cải thiện việc đặt tên, chúng tôi cố ý giữ nó dài dòng. Lưu ý rằng không cần nắm bắt ngữ cảnh bên trong trình bao bọc vì nó đã được ghi lại trong trang web cuộc gọi, do đó ConfigureAwait(false).

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.