try / catch / finallyで待つための良い解決策はありますか?


92

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

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

ただし、残念ながらawaitcatchまたはfinallyブロックでは使用できません。それは、コンパイラーがcatchブロック内に戻ってawait命令の後にあるものを実行する方法がないためだと学びました...

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

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

だから私の質問は:このコードは大丈夫だと思いますか?

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


2
awaitC#6.0以降、Catchブロックでの使用が実際に許可されています(以下の私の回答を参照)
Adi Lester

3
関連するC#5.0エラーメッセージ:CS1985catch句の本文では 待機できません。CS1984finally節の本文で
DavidRR

回答:


172

ロジックを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がスローされた場所を記録します。


1
ExceptionDispatchInfo代わりにException(Stephen Clearyの回答のように)保持する利点は何ですか?
Varvara Kalinina 2017年

2
を再スローすることにした場合Exception、以前のすべてを失うと思いますStackTraceか?
Varvara Kalinina 2017年

1
@VarvaraKalininaそのとおりです。

54

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

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

今述べた機能を含む、新しいC#6.0機能は、ここまたはビデオとしてここにリストされています


C#6.0のcatch / finallyブロックでの待機のサポートもWikipediaに示されています。
DavidRR

4
@DavidRR。ウィキペディアは信頼できません。これに関する限り、これは数百万人に及ぶ別のWebサイトです。
user34660 2017年

これはC#6に有効ですが、質問には最初からC#5のタグが付けられていました。これは、ここでこの答えを持つのが混乱するのか、またはこれらの場合に特定のバージョンタグを削除するだけなのか疑問に思います。
julealgon 2018

16

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

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

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

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

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


1
ありがとうございましたが、残念ながら私はすでにこの方法を知っています。それは私が通常行うことですが、ここではできません。ステートメントで単に使用するthrow exception;if、スタックトレースが失われます。
user2397050 2013年

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.