72

私は古いファイアアンドフォーゲット呼び出しを新しい構文に置き換えようとしています。もっと単純になることを望んでいますが、それは私にはわかりにくいようです。これが例です

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メソッドを書き直す必要があります。パフォーマンス/クリーンアップに関して目に見えない利点はありますか?

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

編集:私は今、私が本当の質問を逃しているかもしれないのを見ます。問題は次のとおりです。同期メソッドA()が与えられた場合、「古い方法」よりも複雑なソリューションを取得せずに、ファイアアンドフォーゲット方式で async/を使用して非同期的に呼び出すにはどうすればよいですか。await

4

5 に答える 5

113

避けてくださいasync void。エラー処理に関してはトリッキーなセマンティクスがあります。「火をつけて忘れる」と言う人もいますが、私は通常「火をつけて墜落する」というフレーズを使います。

問題は次のとおりです。同期メソッドA()が与えられた場合、「古い方法」よりも複雑なソリューションを取得せずに、async / awaitを使用して、ファイアアンドフォーゲット方式で非同期に呼び出すにはどうすればよいですか。

async/は必要ありませんawait。このように呼んでください:

Task.Run(A);
于 2012-10-09T16:06:02.890 に答える
68

他の回答で述べられているように、そしてこの優れたブログ投稿async voidでは、UIイベントハンドラーの外部での使用を避けたいと考えています。安全な「ファイアアンドフォーゲット」メソッドが必要な場合は、次の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

これにより、実際に通常どおりに使用したい場合に、このメソッドを「ファイアアンドフォーゲット」メソッドとして使用しないオプションも提供されます。asyncawait

于 2015-01-09T01:19:52.560 に答える
22

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

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

そして、あなたは大丈夫でしょう。

于 2012-10-09T15:26:47.467 に答える
1

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

主な違いは、待機間のブロックが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;
    }
于 2012-10-09T15:16:08.117 に答える
1

これは、そのような構成の構築に関するベン・アダムスのツイートに基づいて私がまとめたクラスです。HTH https://twitter.com/ben_a_adams/status/1045060828700037125

using Microsoft.Extensions.Logging;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;

// ReSharper disable CheckNamespace
namespace System.Threading.Tasks
{
    public static class TaskExtensions
    {
        [SuppressMessage("ReSharper", "VariableHidesOuterVariable", Justification = "Pass params explicitly to async local function or it will allocate to pass them")]
        public static void Forget(this Task task, ILogger logger = null, [CallerMemberName] string callingMethodName = "")
        {
            if (task == null) throw new ArgumentNullException(nameof(task));

            // Allocate the async/await state machine only when needed for performance reasons.
            // More info about the state machine: https://blogs.msdn.microsoft.com/seteplia/2017/11/30/dissecting-the-async-methods-in-c/?WT.mc_id=DT-MVP-5003978
            // Pass params explicitly to async local function or it will allocate to pass them
            static async Task ForgetAwaited(Task task, ILogger logger = null, string callingMethodName = "")
            {
                try
                {
                    await task;
                }
                catch (TaskCanceledException tce)
                {
                    // log a message if we were given a logger to use
                    logger?.LogError(tce, $"Fire and forget task was canceled for calling method: {callingMethodName}");
                }
                catch (Exception e)
                {
                    // log a message if we were given a logger to use
                    logger?.LogError(e, $"Fire and forget task failed for calling method: {callingMethodName}");
                }
            }

            // note: this code is inspired by a tweet from Ben Adams: https://twitter.com/ben_a_adams/status/1045060828700037125
            // Only care about tasks that may fault (not completed) or are faulted,
            // so fast-path for SuccessfullyCompleted and Canceled tasks.
            if (!task.IsCanceled && (!task.IsCompleted || task.IsFaulted))
            {
                // use "_" (Discard operation) to remove the warning IDE0058: Because this call is not awaited, execution of the
                // current method continues before the call is completed - https://docs.microsoft.com/en-us/dotnet/csharp/discards#a-standalone-discard
                _ = ForgetAwaited(task, logger, callingMethodName);
            }
        }
    }
}
于 2021-06-07T18:45:56.123 に答える