web-dev-qa-db-ja.com

try / catch / finallyで待つための良い解決策は?

次のように(スタックトレースと共に)再び例外をスローする前に、asyncブロックでcatchメソッドを呼び出す必要があります。

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

ただし、残念ながら、awaitまたはcatchブロックでfinallyを使用することはできません。コンパイラがcatch命令のようなものを実行するためにawaitブロックに戻る方法がないためだとわかった...

Task.Wait()を使用してawaitを置き換えようとしましたが、デッドロックが発生しました。これを回避する方法をWebで検索し、 このサイト を見つけました。

asyncメソッドを変更することも、ConfigureAwait(false)を使用するかどうかわからないため、これらのメソッドを作成して、非同期メソッドを開始する_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());
}
_

だから私の質問は次のとおりです。このコードは大丈夫だと思いますか?

もちろん、あなたがいくつかの機能強化を持っているか、より良いアプローチを知っているなら、私は聞いています! :)

89
user2397050

必要に応じて catch を使用することで、ロジックを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の内部でスローされた場所を記録します。

168
user743382

C#6.0以降、awaitおよびcatchブロックでfinallyを使用できることを知っておく必要があるため、実際にこれを行うことができます。

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

先ほど触れたものを含む新しいC#6.0の機能 ここにリストされています またはビデオとして ここ

53
Adi Lester

asyncエラーハンドラを使用する必要がある場合は、次のようなものをお勧めします。

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

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

asyncコードの同期ブロックの問題は(実行しているスレッドに関係なく)、同期ブロックしていることです。ほとんどのシナリオでは、awaitを使用することをお勧めします。

更新:再スローする必要があるため、ExceptionDispatchInfoを使用できます。

15
Stephen Cleary

プロジェクトの次の再利用可能なユーティリティクラスに hvd's great answer を抽出しました。

_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)です。

3
mrts