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

다음 과 같이 예외 (스택 추적 포함)를 다시 던지기 전에 블록 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());
}

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

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



답변

를 사용하여 논리를 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합니다.


답변

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

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

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


답변

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

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

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

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

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


답변

프로젝트에서 재사용 가능한 다음 유틸리티 클래스에 대한 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).


답변