私はこの質問が本当に好きです:
C#でファイアアンドフォーゲットメソッドを実行する最も簡単な方法は?
C#4.0にParallel拡張機能があるので、Parallel linqでFire&Forgetを実行するためのよりクリーンな方法があることを知りたいだけです。
私はこの質問が本当に好きです:
C#でファイアアンドフォーゲットメソッドを実行する最も簡単な方法は?
C#4.0にParallel拡張機能があるので、Parallel linqでFire&Forgetを実行するためのよりクリーンな方法があることを知りたいだけです。
4.0の答えではありませんが、.Net 4.5ではこれをさらに簡単にすることができます:
#pragma warning disable 4014
Task.Run(() =>
{
MyFireAndForgetMethod();
}).ConfigureAwait(false);
#pragma warning restore 4014
プラグマは、このタスクをファイア アンド フォーゲットとして実行していることを知らせる警告を無効にすることです。
中括弧内のメソッドが Task を返す場合:
#pragma warning disable 4014
Task.Run(async () =>
{
await MyFireAndForgetMethod();
}).ConfigureAwait(false);
#pragma warning restore 4014
それを分解しましょう:
Task.Run は、このコードがバックグラウンドで実行されることを示すコンパイラ警告 (警告 CS4014) を生成する Task を返します。
デフォルトでは、タスクは「元のスレッドにマーシャリング」しようとします。これは、このタスクがバックグラウンドで実行され、それを開始したスレッドに戻ろうとすることを意味します。多くの場合、元のスレッドが完了した後にタスクを起動して忘れます。これにより、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();
}
クラスはTask
yes ですが、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());
この質問に対する主要な回答にはいくつかの問題があります。
まず、真のファイア アンド フォーゲットの状況では、タスクを実行しない可能性が高いawait
ため、 を追加しても意味がありませんConfigureAwait(false)
。await
によって返された値を使用しないConfigureAwait
と、効果が得られない可能性があります。
次に、タスクが例外で完了した場合に何が起こるかを認識する必要があります。@ade-miller が提案した簡単な解決策を検討してください。
Task.Factory.StartNew(SomeMethod); // .NET 4.0
Task.Run(SomeMethod); // .NET 4.5
これにより危険が生じます。未処理の例外が からエスケープされた場合SomeMethod()
、その例外は観察されず、ファイナライザー スレッドで再スローされ、アプリケーションがクラッシュする可能性があります。したがって、ヘルパー メソッドを使用して、結果として生じる例外が確実に監視されるようにすることをお勧めします。
次のように書くことができます。
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 が必要な場合でも、監視されないタスクの例外が無害であると想定しないでください。