try / catch / finally에서 await에 대한 좋은 솔루션입니까?


93

다음 과 같이 예외 (스택 추적 포함)를 다시 던지기 전에 블록 async에서 메서드 를 호출해야합니다 catch.

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

그러나 불행히도 당신은 또는 블록 await에서 사용할 수 없습니다 . 나는 컴파일러가 당신의 명령이나 그와 비슷한 것을 실행하기 위해 블록으로 돌아갈 방법이 없기 때문에 배웠습니다 ...catchfinallycatchawait

Task.Wait()교체 에 사용하려고했는데 await교착 상태가 발생했습니다. 나는 이것을 피할 수있는 방법을 웹에서 검색했고이 사이트를 찾았다 .

async메서드를 변경할 수 없고을 사용하는지 알 수 없기 때문에 다른 스레드 (교착 상태를 피하기 위해)에있을 때 비동기 메서드를 시작하고 완료를 기다리는 ConfigureAwait(false)a Func<Task>를 사용하는 다음 메서드를 만들었습니다 .

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

제 질문은 :이 코드가 괜찮다고 생각하십니까?

물론 개선 사항이 있거나 더 나은 접근 방식을 알고 있다면 듣고 있습니다! :)


2
awaitcatch 블록에서 사용 하는 것은 실제로 C # 6.0 이후로 허용됩니다 (아래 내 답변 참조)
Adi Lester

3
관련 C # 5.0 오류 메시지 : CS1985 : catch 절 본문에서 기다릴 수 없습니다. CS1984 : finally 절의 본문에서 기다릴 수 없습니다.
DavidRR

답변:


173

를 사용하여 논리를 catch블록 외부로 이동하고 필요한 경우 예외를 다시 throw 할 수 있습니다 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();
    }
}

이렇게하면 호출자가 예외의 StackTrace속성을 검사 할 때 예외가 발생한 위치를 기록 TaskThatFails합니다.


1
(Stephen Cleary의 답변에서와 같이) ExceptionDispatchInfo대신 유지 하는 것의 장점은 무엇입니까 Exception?
Varvara Kalinina

2
을 (를) 다시 던지기로 결정 Exception하면 이전 항목을 모두 잃는 것 같습니다 StackTrace.
Varvara Kalinina

1
@VarvaraKalinina 정확합니다.

54

C # 6.0부터 awaitin catchfinally블록 을 사용할 수 있으므로 실제로 다음과 같이 할 수 있습니다.

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

방금 언급 한 기능을 포함한 새로운 C # 6.0 기능 은 여기에 나열되어 있거나 여기 에 비디오로 나와 있습니다 .


C # 6.0의 catch / finally 블록 에서 await에 대한 지원 도 Wikipedia에 표시되어 있습니다.
DavidRR

4
@DavidRR. 위키 백과는 권위가 없습니다. 이것에 관한 한 수백만의 또 다른 웹 사이트입니다.
user34660

이것은 C # 6에 유효하지만 질문은 처음부터 C # 5 태그가 지정되었습니다. 이것은 여기 에이 대답이 혼란 스럽거나 이러한 경우 특정 버전 태그를 제거 해야하는지 궁금합니다.
julealgon

16

async오류 처리기 를 사용해야하는 경우 다음과 같은 것이 좋습니다.

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

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

async코드 에서 동 기적으로 차단하는 문제 (실행중인 스레드에 관계없이)는 동 기적으로 차단한다는 것입니다. 대부분의 시나리오에서 await.

업데이트 : 다시 던져야하므로 ExceptionDispatchInfo.


1
고맙지 만 안타깝게도 이미이 방법을 알고 있습니다. 그게 제가 평소에하는 일이지만 여기서는 할 수 없습니다. 문 throw exception;에서 단순히 사용 if하면 스택 추적이 손실됩니다.
user2397050

3

프로젝트에서 재사용 가능한 다음 유틸리티 클래스에 대한 hvd의 훌륭한 답변 을 추출 했습니다 .

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

다음과 같이 사용합니다.

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

이름 지정을 자유롭게 개선 할 수 있습니다. 의도적으로 장황하게 유지했습니다. 호출 사이트에서 이미 캡처되었으므로 래퍼 내부의 컨텍스트를 캡처 할 필요가 없습니다 ConfigureAwait(false).

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.