C#4.0でFire and Forgetメソッドを実行する最も簡単な方法


99

私はこの質問が本当に好きです:

C#でfire and forgetメソッドを実行する最も簡単な方法は?

C#4.0にParallel拡張機能が追加されたので、Parallel linqでFire&Forgetを実行するためのよりクリーンな方法があることを知りたいだけです。


1
その質問への答えはまだ.NET 4.0に適用されます。Fire and forgetはQueueUserWorkItemよりも単純ではありません。
ブライアンラスムッセン2011

回答:


107

4.0の答えではありませんが、.Net 4.5ではこれをさらに簡単にすることができます:

#pragma warning disable 4014
Task.Run(() =>
{
    MyFireAndForgetMethod();
}).ConfigureAwait(false);
#pragma warning restore 4014

プラグマは、このタスクを実行して忘れることを通知する警告を無効にすることです。

中括弧内のメソッドがタスクを返す場合:

#pragma warning disable 4014
Task.Run(async () =>
{
    await MyFireAndForgetMethod();
}).ConfigureAwait(false);
#pragma warning restore 4014

それを分解してみましょう:

Task.Runは、このコードがバックグラウンドで実行されることを通知するコンパイラ警告(CS4014を警告)を生成するTaskを返します-これはまさに望んだとおりなので、警告4014を無効にします。

デフォルトでは、タスクは「元のスレッドへのマーシャリング」を試みます。つまり、このタスクはバックグラウンドで実行され、それを開始したスレッドに戻ろうとします。多くの場合、元のスレッドが完了した後にタスクを実行して忘れることが終了します。これにより、ThreadAbortExceptionがスローされます。ほとんどの場合、これは無害です-それはあなたに言っているだけです、私は再参加しようとしました、私は失敗しましたが、とにかくあなたは気にしません。しかし、本番環境のログまたはローカル開発環境のデバッガーにThreadAbortExceptionsを含めることは、まだ少しうるさいです。.ConfigureAwait(false)整頓されたままで、明示的に言う、これをバックグラウンドで実行する方法にすぎません。

これは冗長で、特に醜いプラグマなので、ライブラリメソッドを使用します。

public static class TaskHelper
{
    /// <summary>
    /// Runs a TPL Task fire-and-forget style, the right way - in the
    /// background, separate from the current thread, with no risk
    /// of it trying to rejoin the current thread.
    /// </summary>
    public static void RunBg(Func<Task> fn)
    {
        Task.Run(fn).ConfigureAwait(false);
    }

    /// <summary>
    /// Runs a task fire-and-forget style and notifies the TPL that this
    /// will not need a Thread to resume on for a long time, or that there
    /// are multiple gaps in thread use that may be long.
    /// Use for example when talking to a slow webservice.
    /// </summary>
    public static void RunBgLong(Func<Task> fn)
    {
        Task.Factory.StartNew(fn, TaskCreationOptions.LongRunning)
            .ConfigureAwait(false);
    }
}

使用法:

TaskHelper.RunBg(async () =>
{
    await doSomethingAsync();
}

7
@ksm残念ながら、あなたのアプローチは問題を抱えています-あなたはそれをテストしましたか?そのアプローチこそが、警告4014が存在する理由です。待機せずに、またTask.Run ...の助けを借りずに非同期メソッドを呼び出すと、そのメソッドは実行されますが、終了すると、元のスレッドにマーシャリングして元のスレッドに戻そうとします。多くの場合、そのスレッドはすでに実行を完了しており、コードは混乱し、不確定な方法で爆発します。やめろ!Task.Runの呼び出しは、「グローバルコンテキストでこれを実行する」と言って、マーシャリングを試行するための何も残さない便利な方法です。
Chris Moschini、2014年

1
@ChrisMoschiniは助けてくれてうれしいです、そしてアップデートをありがとう!一方、上のコメントで「非同期のfuncで呼び出しています」と書いたところ、正直に混乱しています。私が知っているのは、非同期が呼び出し側の(無名関数)コードに含まれていない場合にコードが機能することだけですが、それは呼び出し側のコードが非同期に実行されないことを意味します(これは悪い、非常に悪いことです)?だから私は非同期なしではお勧めしません、この場合、両方が機能するのは奇妙です。
Nicholas Petersen

8
私はポイントが表示されないConfigureAwait(false)Task.Run、あなたがいない時にawaitタスクを。関数の目的はその名前にあります:「構成待ち」。awaitタスクを行わない場合、継続は登録されず、タスクが「元のスレッドにマーシャリングして戻す」ためのコードはありません。より大きなリスクは、ファイナライザスレッドで監視されない例外が再スローされることです。この回答では対処していません。
Mike Strobel、2016年

3
@stricqここでは非同期のvoidの使用はありません。async()=> ...を参照している場合、シグネチャは、空ではなく、タスクを返すFuncです。
クリスモスキーニ

2
VB.netで警告を抑制する:#Disable Warning BC42358
Altiano Gerung、2018

85

ではTaskクラスはい、しかしPLINQは、コレクション上で照会のために実際にあります。

次のようなものはタスクでそれを行います。

Task.Factory.StartNew(() => FireAway());

あるいは...

Task.Factory.StartNew(FireAway);

または...

new Task(FireAway).Start();

どこFireAway

public static void FireAway()
{
    // Blah...
}

したがって、クラスとメソッド名の簡潔さのおかげで、これはスレッドプールのバージョンよりも、選択した文字に応じて6〜19文字ほど優れています。

ThreadPool.QueueUserWorkItem(o => FireAway());

確かにそれらは同等の機能ではありませんか?
Jonathon Kresner、2011

6
StartNewと新しいTask.Startの間にはわずかな意味上の違いがありますが、それ以外の場合はそうです。それらはすべて、FireAwayをキューに入れてスレッドプールのスレッドで実行します。
Ade Miller

このケースのために働く:fire and forget in ASP.NET WebForms and windows.close()
PreguntonCojoneroCabrón

32

この質問の主な答えにはいくつか問題があります。

1つ目は、本当の火と忘却の状況では、おそらくawaitタスクを実行しないため、を追加しても意味がありませんConfigureAwait(false)。そうしない場合awaitで返された値をConfigureAwait、それはおそらく何の効果を持つことができません。

次に、タスクが例外で完了したときに何が起こるかを認識する必要があります。@ ade-millerが提案した簡単な解決策を考えてみましょう:

Task.Factory.StartNew(SomeMethod);  // .NET 4.0
Task.Run(SomeMethod);               // .NET 4.5

この発表の危険は:から未処理の例外が逃げる場合SomeMethod()、その例外は認められことはありません、とも1は、アプリケーションをクラッシュ、ファイナライザスレッドに再スローします。したがって、ヘルパーメソッドを使用して、結果として生じる例外が確実に監視されるようにすることをお勧めします。

あなたはこのようなものを書くことができます:

public static class Blindly
{
    private static readonly Action<Task> DefaultErrorContinuation =
        t =>
        {
            try { t.Wait(); }
            catch {}
        };

    public static void Run(Action action, Action<Exception> handler = null)
    {
        if (action == null)
            throw new ArgumentNullException(nameof(action));

        var task = Task.Run(action);  // Adapt as necessary for .NET 4.0.

        if (handler == null)
        {
            task.ContinueWith(
                DefaultErrorContinuation,
                TaskContinuationOptions.ExecuteSynchronously |
                TaskContinuationOptions.OnlyOnFaulted);
        }
        else
        {
            task.ContinueWith(
                t => handler(t.Exception.GetBaseException()),
                TaskContinuationOptions.ExecuteSynchronously |
                TaskContinuationOptions.OnlyOnFaulted);
        }
    }
}

この実装のオーバーヘッドは最小限である必要があります。継続は、タスクが正常に完了しない場合にのみ呼び出され、同期で呼び出される必要があります(元のタスクとは別にスケジュールされるのではありません)。「レイジー」の場合、継続デリゲートの割り当ても発生しません。

非同期操作のキックオフは簡単になります。

Blindly.Run(SomeMethod);                              // Ignore error
Blindly.Run(SomeMethod, e => Log.Warn("Whoops", e));  // Log error

1.これは.NET 4.0のデフォルトの動作でした。.NET 4.5では、デフォルトの動作が変更され、監視されていない例外がファイナライザスレッドで再スローされないようになりました(ただし、TaskSchedulerのUnobservedTaskExceptionイベントを介してそれらを引き続き監視できます)。ただし、デフォルトの構成は上書き可能であり、アプリケーションに.NET 4.5が必要な場合でも、監視されていないタスクの例外が無害であると想定しないでください。


1
ハンドラーが渡され、ContinueWithキャンセルによりが呼び出された場合、前件の例外プロパティはNullになり、null参照例外がスローされます。タスク継続オプションをに設定OnlyOnFaultedすると、nullチェック、または例外を使用する前に例外がnullかどうかをチェックする必要がなくなります。
sanmcp 2016年

Run()メソッドは、さまざまなメソッドシグネチャに対してオーバーライドする必要があります。タスクの拡張メソッドとしてこれを行う方が良いです。
stricq 2017

1
@stricq質問は「ファイアアンドフォーゲット」操作(つまり、ステータスが確認されず、結果が確認されない)について尋ねられたので、これに焦点を当てました。自分の足を最もきれいに撮影する方法が問題である場合、デザインの観点からどのソリューションが「より良い」かを議論することは、かなりおもしろくなりません:)。最良の答えは「ない」間違いなくですが、私はことを回避する問題は、他の回答に提示することを簡潔なソリューションを提供することに焦点を当てて、短い最小応答長の落ちたという。引数はクロージャーで簡単にラップでき、戻り値がないため、using Taskは実装の詳細になります。
Mike Strobel 2017

@stricqとはいえ、私はあなたに反対しません。あなたが提案した代替案は、さまざまな理由にもかかわらず、私が過去にやったこととまったく同じです。「開発者が障害の発生を観察できない場合にアプリをクラッシュさせないようにしたいTask」は、「バックグラウンド操作を開始する簡単な方法が欲しい、その結果を決して観察したくない、どのメカニズムが使用されているかを気にしていない」と同じではありません。それをするために。" その上で、私は尋ねられた質問に対する私の回答を後回しにしています。
Mike Strobel 2017

この場合のために働くfire and forgetにはASP.NET Webフォームwindows.close()
PreguntonCojoneroCabrón

7

Mike Strobelの回答で発生する問題を修正するだけです。

を使用しvar task = Task.Run(action)、そのタスクに継続を割り当てた後Task、例外ハンドラの継続をに割り当てる前に、いくつかの例外をスローするリスクに遭遇しますTask。したがって、以下のクラスにはこのリスクがないはずです。

using System;
using System.Threading.Tasks;

namespace MyNameSpace
{
    public sealed class AsyncManager : IAsyncManager
    {
        private Action<Task> DefaultExeptionHandler = t =>
        {
            try { t.Wait(); }
            catch { /* Swallow the exception */ }
        };

        public Task Run(Action action, Action<Exception> exceptionHandler = null)
        {
            if (action == null) { throw new ArgumentNullException(nameof(action)); }

            var task = new Task(action);

            Action<Task> handler = exceptionHandler != null ?
                new Action<Task>(t => exceptionHandler(t.Exception.GetBaseException())) :
                DefaultExeptionHandler;

            var continuation = task.ContinueWith(handler,
                TaskContinuationOptions.ExecuteSynchronously
                | TaskContinuationOptions.OnlyOnFaulted);
            task.Start();

            return continuation;
        }
    }
}

ここでは、taskは直接実行されず、作成され、継続が割り当てられます。継続が割り当てられる前に、タスクが実行される(または例外がスローされる)リスクを排除するためにタスクが実行されます。

Runここのメソッドは継続を返すTaskので、実行が完了したことを確認する単体テストを作成できます。ただし、使用方法では無視してかまいません。


静的クラスにしないことも意図的ですか?
Deantwo

このクラスはIAsyncManagerインターフェイスを実装しているため、静的クラスにすることはできません。
Mert Akcakaya
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.