web-dev-qa-db-ja.com

非同期と「古い非同期デリゲート」を使用した「消して忘れる」

私は、よりシンプルにすることを望んで、古い火と忘れの呼び出しを新しい構文に置き換えようとしていますが、それは私を避けているようです。ここに例があります

_class Program
{
    static void DoIt(string entry) 
    { 
        Console.WriteLine("Message: " + entry);
    }

    static async void DoIt2(string entry)
    {
        await Task.Yield();
        Console.WriteLine("Message2: " + entry);
    }

    static void Main(string[] args)
    {
        // old way
        Action<string> async = DoIt;
        async.BeginInvoke("Test", ar => { async.EndInvoke(ar); ar.AsyncWaitHandle.Close(); }, null);
        Console.WriteLine("old-way main thread invoker finished");
        // new way
        DoIt2("Test2");   
        Console.WriteLine("new-way main thread invoker finished");
        Console.ReadLine();
    }
}
_

両方のアプローチは同じことをしますが、私が得たように見えるもの(EndInvokeとハンドルを閉じる必要はありませんが、これはまだ議論の余地があります)私はTask.Yield()は、実際に既存のすべての非同期F&Fメソッドを書き換えて、その1行を追加する必要があるという新しい問題を引き起こします。パフォーマンス/クリーンアップに関して目に見えない利益がありますか?

バックグラウンドメソッドを変更できない場合、どのように非同期を適用しますか?直接的な方法はないように思えますが、Task.Run()を待つラッパー非同期メソッドを作成する必要がありますか?

編集:今、私は本当の質問を見逃しているかもしれません。問題は:同期メソッドA()が与えられた場合、async/awaitを使用して非同期的に呼び出すことができます。 「古い方法」

58
mmix

async void。エラー処理に関する扱いにくいセマンティクスがあります。一部の人々はそれを「火事と忘却」と呼んでいますが、私は通常「火事と衝突」というフレーズを使用します。

問題は、同期メソッドA()が与えられた場合、「旧式の方法」よりも複雑なソリューションを取得することなく、async/awaitを使用して非同期で呼び出す方法です。

async/awaitは必要ありません。次のように呼び出してください:

Task.Run(A);
82
Stephen Cleary

他の回答で述べたように、この優れた ブログ投稿 によって、UIイベントハンドラーの外部でasync voidを使用することを避けたいと思います。 safe「fire and forget」asyncメソッドが必要な場合は、このパターンの使用を検討してください(@ReedCopseyの功績。このメソッドは彼が私に与えたものです)チャット会話):

  1. Taskの拡張メソッドを作成します。渡されたTaskを実行し、例外をキャッチ/ログします:

    static async void FireAndForget(this Task task)
    {
       try
       {
            await task;
       }
       catch (Exception e)
       {
           // log errors
       }
    }
    
  2. それらを作成するときは、常にTaskスタイルのasyncメソッドを使用してください。async voidは使用しないでください。

  3. 次の方法でこれらのメソッドを呼び出します。

    MyTaskAsyncMethod().FireAndForget();
    

awaitする必要はありません(また、await警告を生成しません)。また、エラーを正しく処理します。これはasync voidを置く唯一の場所なので、覚えておく必要はありませんtry/catchブロックをどこにでも配置します。

これにより、実際にasyncを通常どおりに使用したい場合に、awaitメソッドを "fire and forget"メソッドとして使用するnotのオプションも提供されます。

49
BradleyDotNET

私にとっては、何かを「待つ」ことと「火と忘却」は直交する2つの概念のようです。メソッドを非同期で開始し、結果を気にしないか、操作の完了後に元のコンテキストで実行を再開したい(場合によっては戻り値を使用する)ことができます。 ThreadPoolスレッドでメソッドを実行したいだけであれば(UIがブロックされないように)、

Task.Factory.StartNew(() => DoIt2("Test2"))

大丈夫です。

18
Daniel C. Weber

私の感覚では、これらの「ファイアアンドフォーゲット」メソッドは主にUIとバックグラウンドコードをインターリーブするクリーンな方法を必要とする成果物であり、ロジックを一連の順次命令として記述することができます。 async/awaitはSynchronizationContextを介したマーシャリングを処理するため、これは問題になりません。長いシーケンスのインラインコードは、以前はバックグラウンドスレッドのルーチンから起動されていた「fire and forget」ブロックになります。これは事実上、パターンの反転です。

主な違いは、待機間のブロックがBeginInvokeよりもInvokeに似ていることです。 BeginInvokeのような動作が必要な場合は、次の非同期メソッドを呼び出して(タスクを返す)、実際に「BeginInvoke」を実行したいコードの後まで、返されたタスクを待たないでください。

    public async void Method()
    {
        //Do UI stuff
        await SomeTaskAsync();
        //Do more UI stuff (as if called via Invoke from a thread)
        var nextTask = NextTaskAsync();
        //Do UI stuff while task is running (as if called via BeginInvoke from a thread)
        await nextTask;
    }
1
Dan Bryant