web-dev-qa-db-ja.com

C#4.0で火事と忘却方法を行う最も簡単な方法

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

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

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

81

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();
}
_
86
Chris Moschini

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());
83
Ade Miller

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

まず、本当のfire-and-forgetの状況では、おそらくタスクを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では、デフォルトの動作が変更され、未観測の例外がnotファイナライザースレッドで再スローされるようになりました(ただし、TaskSchedulerのUnobservedTaskExceptionイベントを介してそれらを監視できます)。ただし、既定の構成はオーバーライドできます。アプリケーションで.NET 4.5が必要な場合でも、監視されていないタスクの例外が無害であると想定しないでください。

24
Mike Strobel

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を返すので、実行が完了したことを確認する単体テストを書くことができます。ただし、使用中は無視しても問題ありません。

6
Mert Akcakaya