async / await-Task vs voidを返すとき?


503

どんなシナリオを使いたいのか

public async Task AsyncMethod(int num)

の代わりに

public async void AsyncMethod(int num)

私が考えることができる唯一のシナリオは、タスクがその進捗状況を追跡できるようにする必要がある場合です。

さらに、次のメソッドでは、asyncおよびawaitキーワードは不要ですか?

public static async void AsyncMethod2(int num)
{
    await Task.Factory.StartNew(() => Thread.Sleep(num));
}

20
非同期メソッドは、必要があることを注意必ず名前Async.Exampleを付けることFoo()になりますFooAsync()
フレッド

30
@フレッドほとんどが、常にではありません。これは単なる規則であり、この規則の受け入れられている例外は、イベントベースのクラスまたはインターフェイスコントラクトに関するものです。MSDNを参照してください。たとえば、Button1_Clickなどの一般的なイベントハンドラの名前は変更しないでください。
ベン

14
代わりにThread.Sleep、タスクで使用すべきではないメモawait Task.Delay(num)
Bob Vale

45
@fredこれに同意しません。非同期サフィックスを追加するIMOは、同期オプションと非同期オプションの両方を備えたインターフェースを提供する場合にのみ使用してください。スマーフが意図を1つしか持たない場合に非同期で名前を付けるのは無意味です。タスクのすべてのメソッドが非同期であるため、適切な例でTask.DelayはありませんTask.AsyncDelay
愛されていません

11
今朝、webapi 2コントローラーメソッドで興味深い問題がありましasync voidasync Task。代わりに宣言されました。メソッドの実行が完了する前に破棄されたコントローラーのメンバーとして宣言されたEntity Frameworkコンテキストオブジェクトを使用していたため、メソッドがクラッシュしました。フレームワークは、メソッドの実行が完了する前にコントローラーを破棄しました。私はメソッドを非同期タスクに変更し、それが機能しました。
コスタ

回答:


417

1)通常はを返しますTask。主な例外は、(イベントの)戻り値の型が必要な場合ですvoid。発信者にawaitあなたのタスクを許可しない理由がない場合、なぜそれを禁止するのですか?

2)async戻るメソッドvoidは別の側面で特別です。これらはトップレベルの非同期操作を表し、タスクが例外を返すときに機能する追加のルールがあります。違いを示す最も簡単な方法は、例を使用することです。

static async void f()
{
    await h();
}

static async Task g()
{
    await h();
}

static async Task h()
{
    throw new NotImplementedException();
}

private void button1_Click(object sender, EventArgs e)
{
    f();
}

private void button2_Click(object sender, EventArgs e)
{
    g();
}

private void button3_Click(object sender, EventArgs e)
{
    GC.Collect();
}

fの例外は常に「監視」されます。トップレベルの非同期メソッドを離れる例外は、他の未処理の例外と同様に扱われます。gの例外は確認されません。ガベージコレクターがタスクをクリーンアップすると、タスクによって例外が発生したことがわかり、誰も例外を処理しませんでした。これが発生すると、TaskScheduler.UnobservedTaskExceptionハンドラーが実行されます。これを起こさせてはいけません。あなたの例を使用するには、

public static async void AsyncMethod2(int num)
{
    await Task.Factory.StartNew(() => Thread.Sleep(num));
}

はい、使用asyncawaitます。ここでは、例外がスローされた場合でもメソッドが正しく機能することを確認しています。

詳細については、http//msdn.microsoft.com/en-us/magazine/jj991977.aspxを参照してください。


10
私はコメントのf代わりに意味しましたg。からの例外fはに渡されますSynchronizationContextg上げないだろうUnobservedTaskExceptionが、UTEそれは取り扱っていない場合は、もはやプロセスがクラッシュします。このような「非同期例外」が無視されても問題ない状況がいくつかあります。
スティーブンクリアリー

3
あなたは持っている場合はWhenAny、複数でTasksが例外になります。多くの場合、最初の1つだけを処理すればよく、他の1つは無視したいことがよくあります。
スティーブンクリアリー

1
@StephenClearyありがとう、WhenAny他の例外を無視しても大丈夫かどうかはそもそも呼び出す理由に依存しますが、それは良い例だと思います。 、例外ありまたはなし。

10
voidの代わりにTaskを返すことをお勧めする理由について少し混乱しています。あなたが言ったように、f()は例外をスローしますが、g()はスローしません。これらのバックグラウンドスレッドの例外を認識するのが最善ではありませんか?
user981225 2012

2
@ user981225確かに、それがgの呼び出し元の責任になります。gを呼び出すすべてのメソッドは非同期で、awaitも使用する必要があります。これはガイドラインであり、ハードルールではありません。特定のプログラムでは、gを無効にする方が簡単であると判断できます。

40

私は約この非常に便利な記事に遭遇しているasyncvoidジェローム・ラバンによって書かれた: https://jaylee.org/archive/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void .html

つまり、async+voidはシステムをクラッシュさせる可能性があり、通常はUI側のイベントハンドラーでのみ使用する必要があります。

この背後にある理由は、AsyncVoidMethodBuilderによって使用される同期コンテキストであり、この例ではありません。アンビエント同期コンテキストがない場合、非同期voidメソッドの本体によって処理されない例外は、ThreadPoolに再スローされます。この種の未処理の例外がスローされる可能性のある論理的な場所は他にないようですが、.NET 2.0以降、ThreadPoolの未処理の例外がプロセスを効果的に終了するため、残念ながらプロセスは終了します。AppDomain.UnhandledExceptionイベントを使用して、すべての未処理の例外をインターセプトできますが、このイベントからプロセスを回復する方法はありません。

UIイベントハンドラーを作成する場合、例外は非同期ではないメソッドで見られるのと同じように扱われるため、非同期のvoidメソッドはなんとなく簡単です。それらはDispatcherにスローされます。このような例外から回復する可能性がありますが、ほとんどの場合、これで十分です。ただし、UIイベントハンドラの外では、async voidメソッドを使用するのはやや危険であり、見つけにくい場合があります。


あれは正しいですか?非同期メソッド内の例外はタスク内でキャプチャされると思いました。そしてまた、私の知る限り、常にdeafaultのSynchronizationContextがあります
NM

30

私はこの発言から明確な考えを得ました。

  1. 非同期voidメソッドには、異なるエラー処理セマンティクスがあります。非同期タスクまたは非同期タスクメソッドから例外がスローされると、その例外がキャプチャされ、タスクオブジェクトに配置されます。async voidメソッドを使用すると、Taskオブジェクトがないため、async voidメソッドからスローされた例外は、SynchronizationContextで直接発生します(SynchronizationContextは、コードが実行される可能性がある「場所」を表します。)async voidメソッドのときにアクティブでした。始めた

非同期Voidメソッドからの例外をキャッチでキャッチできない

private async void ThrowExceptionAsync()
{
  throw new InvalidOperationException();
}
public void AsyncVoidExceptions_CannotBeCaughtByCatch()
{
  try
  {
    ThrowExceptionAsync();
  }
  catch (Exception)
  {
    // The exception is never caught here!
    throw;
  }
}

これらの例外は、AppDomain.UnhandledExceptionまたはGUI / ASP.NETアプリケーションの同様のキャッチオールイベントを使用して監視できますが、これらのイベントを通常の例外処理に使用すると、保守性が失われます(アプリケーションがクラッシュします)。

  1. 非同期voidメソッドには、異なる構成セマンティクスがあります。TaskまたはTaskを返す非同期メソッドは、await、Task.WhenAny、Task.WhenAllなどを使用して簡単に構成できます。voidを返す非同期メソッドは、呼び出しコードに完了したことを通知する簡単な方法を提供しません。いくつかの非同期voidメソッドを開始するのは簡単ですが、いつ終了したかを判別するのは簡単ではありません。Async voidメソッドは、開始および終了時にSynchronizationContextに通知しますが、カスタムSynchronizationContextは通常のアプリケーションコードの複雑なソリューションです。

  2. 非同期Voidメソッドは、同期イベントハンドラーを使用する場合に役立ちます。同期イベントハンドラーの動作と同様に、SynchronizationContextで直接例外が発生するためです。

詳細については、このリンクを確認してください https://msdn.microsoft.com/en-us/magazine/jj991977.aspx


21

async voidを呼び出すことの問題は、タスクを取り戻すことさえできず、関数のタスクがいつ完了したかを知る方法がないことです(https://blogs.msdn.microsoft.com/oldnewthing/20170720-00/を参照)。 ?p = 96655

非同期関数を呼び出す3つの方法は次のとおりです。

async Task<T> SomethingAsync() { ... return t; }
async Task SomethingAsync() { ... }
async void SomethingAsync() { ... }

すべての場合において、関数は一連のタスクに変換されます。違いは、関数が返すものです。

最初のケースでは、関数は最終的にtを生成するタスクを返します。

2番目のケースでは、関数は製品を持たないタスクを返しますが、実行が完了するまで待機することができます。

3番目のケースは厄介なケースです。3番目のケースは2番目のケースと同様ですが、タスクを取り戻すことさえできません。関数のタスクがいつ完了したかを知る方法はありません。

非同期voidのケースは「ファイアアンドフォーゲット」です。タスクチェーンを開始しますが、いつ終了するかは気にしません。関数が戻るとわかるのは、最初の待機まですべてが実行されたことです。最初の待機後のすべては、将来アクセスできない特定の場所で実行されます。


6

async void例外をキャッチするように注意している限り、バックグラウンド操作のキックオフにも使用できると思います。考え?

class Program {

    static bool isFinished = false;

    static void Main(string[] args) {

        // Kick off the background operation and don't care about when it completes
        BackgroundWork();

        Console.WriteLine("Press enter when you're ready to stop the background operation.");
        Console.ReadLine();
        isFinished = true;
    }

    // Using async void to kickoff a background operation that nobody wants to be notified about when it completes.
    static async void BackgroundWork() {
        // It's important to catch exceptions so we don't crash the appliation.
        try {
            // This operation will end after ten interations or when the app closes. Whichever happens first.
            for (var count = 1; count <= 10 && !isFinished; count++) {
                await Task.Delay(1000);
                Console.WriteLine($"{count} seconds of work elapsed.");
            }
            Console.WriteLine("Background operation came to an end.");
        } catch (Exception x) {
            Console.WriteLine("Caught exception:");
            Console.WriteLine(x.ToString());
        }
    }
}

1
async voidを呼び出すことの問題は、タスクを取り戻すことさえできず、関数のタスクが
いつ

これは、結果を気にせず、例外が他の操作に影響を与えたくない(まれな)バックグラウンド操作には意味があります。典型的な使用例は、ログイベントをログサーバーに送信することです。これをバックグラウンドで実行し、ログサーバーがダウンした場合にサービス/リクエストハンドラが失敗しないようにする必要があります。もちろん、すべての例外をキャッチする必要があります。そうしないと、プロセスが終了します。またはこれを達成するためのより良い方法はありますか?
フロリアン冬

非同期Voidメソッドからの例外をキャッチでキャッチすることはできません。msdn.microsoft.com/en-us/magazine/jj991977.aspx
Si Zi

3
@SiZiキャッチは、例に示すように非同期voidメソッド内にあり、キャッチされます。
bboyle1234 2018年

要件を変更して、ログに記録できない場合は何もする必要がないように変更した場合はどうでしょうか。現在//例外を無視しているロジックを変更するだけでなく、API全体で署名を変更する必要があります。
Milney

0

私の答えは簡単で、voidメソッドを待つことはできません

Error   CS4008  Cannot await 'void' TestAsync   e:\test\TestAsync\TestAsyncProgram.cs

したがって、メソッドが非同期の場合は、非同期の利点が失われる可能性があるため、待機できる方が良いです。


-2

Microsoftのドキュメントよると、絶対に使用しないでくださいasync void

これを行わないでください。次の例ではasync void、最初の待機時間に達したときにHTTPリクエストを完了するようにしています。

  • ASP.NET Coreアプリでは、これは常に悪い習慣です。

  • HTTPリクエストの完了後にHttpResponseにアクセスします。

  • プロセスをクラッシュさせます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.