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


150

私はWCFでそれらが[OperationContract(IsOneWay = true)]属性を持っているのを見ました。しかし、WCFは、ノンブロッキング関数を作成するためだけに、遅くて重いようです。static void nonblockingのようなものが理想的ですがMethodFoo(){}、それは存在しないと思います。

C#でノンブロッキングメソッドコールを作成する最も速い方法は何ですか?

例えば

class Foo
{
    static void Main()
    {
        FireAway(); //No callback, just go away
        Console.WriteLine("Happens immediately");
    }

    static void FireAway()
    {
        System.Threading.Thread.Sleep(5000);
        Console.WriteLine("5 seconds later");
    }
}

注意:これを読んでいる人はみな、メソッドを実際に終了させたいかどうかを検討する必要があります。(#2のトップアンサーを参照)メソッドを終了する必要がある場合は、ASP.NETアプリケーションなどの一部の場所で、スレッドをブロックして存続させるために何かを行う必要があります。それ以外の場合、これは「fire-forget-but-never-actually-execute」につながる可能性があります。その場合、もちろん、コードをまったく記述しない方が簡単です。(これがASP.NETでどのように機能するかについての良い説明

回答:


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

(5年後...)

Task.Run(() => FireAway());

luisperezphdが指摘したように


あなたが勝ちます。いいえ、実際には、これを別の関数にカプセル化しない限り、これが最も短いバージョンのようです。
OregonGhost 2009年

13
これについて考えてみましょう...ほとんどのアプリケーションではこれで問題ありませんが、FireAwayがコンソールに何かを送信する前にコンソールアプリケーションが終了します。ThreadPoolスレッドはバックグラウンドスレッドであり、アプリが終了すると終了します。一方、FireAwayがウィンドウに書き込もうとする前にコンソールウィンドウが消えてしまうため、スレッドを使用しても機能しません。

3
実行が保証されている非ブロッキングメソッド呼び出しを行う方法はないため、これが実際にIMOの質問に対する最も正確な答えです。実行を保証する必要がある場合、潜在的なブロッキングをAutoResetEvent(Kevが言及したような)などの制御構造を介して導入する必要があります
Guvante

1
スレッドプールとは独立したスレッドでタスクを実行するには、次の方法も使用できると思います(new Action(FireAway)).BeginInvoke()
Sam Harwell

2
これについてはTask.Factory.StartNew(() => myevent());stackoverflow.com / questions / 14858261 /…
Luis Perez 14

39

C#4.0以降の場合、ここでAde Millerが最良の答えを提供していることに感銘を受けました。c#4.0でfire and forgetメソッドを実行する最も簡単な方法

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

あるいは...

Task.Factory.StartNew(FireAway);

または...

new Task(FireAway).Start();

どこFireAway

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

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

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

11
良い質問に対して最良の答えを簡単に利用できるようにすることに最も興味があります。私は間違いなく他の人にも同じことをするよう勧めます。
Patrick Szalapski、2014

3
stackoverflow.com/help/referencingによると、別のソースから引用していることを示すには、ブロック引用符を使用する必要があります。それがあなたの答えであるので、すべてあなた自身の仕事であるように見えます。回答の正確なコピーを再投稿するというあなたの意図は、コメント内のリンクで達成できたはずです。
トムレッドファーン14

1
パラメータを渡す方法はFireAway
GuidoG

私はあなたが最初のソリューションを使用する必要があるだろうと信じて:Task.Factory.StartNew(() => FireAway(foo));
Patrick Szalapski、

1
ここここでTask.Factory.StartNew(() => FireAway(foo));説明されているようにfooをループ内で変更することはできないので注意してください
AaA

22

.NET 4.5の場合:

Task.Run(() => FireAway());

15
Fyi、これは主Task.Factory.StartNew(() => FireAway(), CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx
Jim

2
知っておくと、@ JimGeurts!
David Murdoch 2013年

後世のために、コメントで@JimGeurtsにリンクされた記事は404になりました(ありがとう、MS!)。-そのURL内の日付スタンプを考えると、スティーブンToubによってこの記事は正しいものであるように思われるdevblogs.microsoft.com/pfxteam/...
ダン・アトキンソン

1
@DanAtkinson正解です。ここではarchive.orgで、元はもう一度ケースのMSが移動することで、次のとおりです。web.archive.org/web/20160226022029/http://blogs.msdn.com/b/...
デヴィッド・マードック

18

Willの回答に追加するには、これがコンソールアプリケーションの場合、ワーカースレッドが完了する前に終了AutoResetEventするのWaitHandleを防ぐために、とをスローします。

Using System;
Using System.Threading;

class Foo
{
    static AutoResetEvent autoEvent = new AutoResetEvent(false);

    static void Main()
    {
        ThreadPoolQueueUserWorkItem(new WaitCallback(FireAway), autoEvent);
        autoEvent.WaitOne(); // Will wait for thread to complete
    }

    static void FireAway(object stateInfo)
    {
        System.Threading.Thread.Sleep(5000);
        Console.WriteLine("5 seconds later");
        ((AutoResetEvent)stateInfo).Set();
    }
}

これがコンソールアプリではなく、C#クラスがCOM Visibleである場合、AutoEventは機能しますか?autoEvent.WaitOne()はブロックしていますか?
dan_l 2012年

5
@dan_l-わからない、それを新しい質問として尋ねて、この質問を参照してみませんか。
ケフ2012年

@dan_l、はいWaitOne、常にブロックされ、AutoResetEvent常に機能します
AaA

AutoResetEventが実際に機能するのは、バックグラウンドスレッドが1つの場合のみです。複数のスレッドを使用している場合は、バリアの方が良いのではないでしょうか。docs.microsoft.com/en-us/dotnet/standard/threading/barrier
Martin Brown

@MartinBrown-それが本当かどうかはわかりません。の配列を作成できWaitHandle、それぞれに独自のAutoResetEventと対応するがありThreadPool.QueueUserWorkItemます。その後ちょうどWaitHandle.WaitAll(arrayOfWaitHandles)
Kev

15

簡単な方法は、パラメータなしのラムダでスレッドを作成して開始することです。

(new Thread(() => { 
    FireAway(); 
    MessageBox.Show("FireAway Finished!"); 
}) { 
    Name = "Long Running Work Thread (FireAway Call)",
    Priority = ThreadPriority.BelowNormal 
}).Start();

このメソッドをThreadPool.QueueUserWorkItemで使用することにより、新しいスレッドに名前を付けて、デバッグを容易にすることができます。また、デバッガの外で未処理の例外があるとアプリケーションが突然クラッシュするため、ルーチンで広範なエラー処理を使用することを忘れないでください。

ここに画像の説明を入力してください


+1は、長時間実行プロセスまたはブロックプロセスのために専用スレッドが必要な場合に使用します。
Paul Turner、

12

Asp.Netおよび.Net 4.5.2を使用しているときにこれを行うには、を使用することをお勧めしQueueBackgroundWorkItemます。ここにヘルパークラスがあります:

public static class BackgroundTaskRunner
{     
    public static void FireAndForgetTask(Action action)
    {
        HostingEnvironment.QueueBackgroundWorkItem(cancellationToken => // .Net 4.5.2 required
        {
            try
            {
                action();
            }
            catch (Exception e)
            {
                // TODO: handle exception
            }
        });
    }

    /// <summary>
    /// Using async
    /// </summary>
    public static void FireAndForgetTask(Func<Task> action)
    {
        HostingEnvironment.QueueBackgroundWorkItem(async cancellationToken => // .Net 4.5.2 required
        {
            try
            {
                await action();
            }
            catch (Exception e)
            {
                // TODO: handle exception
            }
        });
    }
}

使用例:

BackgroundTaskRunner.FireAndForgetTask(() =>
{
    FireAway();
});

または非同期を使用して:

BackgroundTaskRunner.FireAndForgetTask(async () =>
{
    await FireAway();
});

これは、Azure Webサイトでうまく機能します。

参照:QueueBackgroundWorkItemを使用して、.NET 4.5.2のASP.NETアプリケーションからバックグラウンドジョブをスケジュールする


7

beginInvokeを呼び出し、EndInvokeをキャッチしないことは良い方法ではありません。答えは簡単です。EndInvokeを呼び出す必要があるのは、呼び出しの結果(戻り値がない場合でも)は、EndInvokeが呼び出されるまで.NETによってキャッシュされる必要があるためです。たとえば、呼び出されたコードが例外をスローした場合、その例外は呼び出しデータにキャッシュされます。EndInvokeを呼び出すまで、メモリに残ります。EndInvokeを呼び出した後、メモリを解放できます。この特定のケースでは、データは呼び出しコードによって内部的に維持されるため、プロセスがシャットダウンするまでメモリが残る可能性があります。GCは最終的にそれを収集する可能性があると思いますが、データを取得するのに非常に長い時間をかけているのではなく、データを放棄したことをGCがどのように認識するかわかりません。疑わしいです。したがって、メモリリークが発生する可能性があります。

詳細はhttp://haacked.com/archive/2009/01/09/asynchronous-fire-and-forget-with-lambdas.aspxにあります。


3
GCは、それらへの参照が存在しない場合、物事が放棄されたことを通知できます。BeginInvokeが実際の結果データを保持するオブジェクトへの参照を保持しているが参照されていないラッパーオブジェクトを返す場合、ラッパーオブジェクトへのすべての参照が破棄されると、ラッパーオブジェクトはファイナライズの対象になります。
スーパーキャット2012年

これは「良いアプローチではない」と私は質問します。懸念されるエラー状態でテストし、問題があるかどうかを確認します。ガベージコレクションが完全ではない場合でも、ほとんどのアプリケーションに大きな影響はありません。Microsoftによれば、「必要に応じて、EndInvokeを呼び出してデリゲートから戻り値を取得できますが、これは必須ではありません。EndInvokeは、戻り値が取得できるまでブロックされます。」from:msdn.microsoft.com/en-us/library/0b1bf3y3(v
Abacus


3

最も単純な.NET 2.0以降のアプローチは、非同期プログラミングモデル(つまり、デリゲートのBeginInvoke)を使用することです。

static void Main(string[] args)
{
      new MethodInvoker(FireAway).BeginInvoke(null, null);

      Console.WriteLine("Main: " + Thread.CurrentThread.ManagedThreadId);

      Thread.Sleep(5000);
}

private static void FireAway()
{
    Thread.Sleep(2000);

    Console.WriteLine("FireAway: " + Thread.CurrentThread.ManagedThreadId );  
}

Task.Run()存在する前は、これはおそらくこれを行う最も簡潔な方法でした。
binki 2016
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.