Task.Run with Parameter(s)?


88

私はマルチタスクネットワークプロジェクトに取り組んでおり、は初めてですThreading.Tasks。シンプルなものを実装しましたが、Task.Factory.StartNew()どうすればそれができるのでしょうか。Task.Run()でしょうか?

基本的なコードは次のとおりです。

Task.Factory.StartNew(new Action<object>(
(x) =>
{
    // Do something with 'x'
}), rawData);

オブジェクトブラウザを調べましSystem.Threading.Tasks.Taskたが、同様のパラメータが見つかりませんでした。パラメータを取るものだけがあり、タイプはありません。Action<T>Actionvoid

そこ似唯一の2つのことは、次のとおりstatic Task Run(Action action)static Task Run(Func<Task> function)が、両方とポストパラメータ(複数可)することはできません。

はい、私はそれのための簡単な拡張メソッドを作成できることを知っていますが、私の主な質問は、それを1行書くことができるTask.Run()かどうかです。


パラメータのをどのようにするかは明確ではありません。それはどこから来るのでしょうか?すでに持っている場合は、ラムダ式でキャプチャしてください...
Jon Skeet

@JonSkeetrawDataは、コンテナクラス(DataPacketなど)を持つネットワークデータパケットであり、このインスタンスを再利用してGCの圧力を軽減しています。したがって、でrawData直接使用する場合はTaskTask処理する前に(おそらく)変更できます。これで、別のbyte[]インスタンスを作成できると思います。それが私にとって最も簡単な解決策だと思います。
MFatihMAR 2015年

はい、バイト配列のクローンを作成する必要がある場合は、バイト配列のクローンを作成します。持ってAction<byte[]>いてもそれは変わりません。
ジョンスキート2015年

ここではいくつかあるの良いソリューションタスクにパラメータを渡すには。
ただ、シャドウ

回答:


116
private void RunAsync()
{
    string param = "Hi";
    Task.Run(() => MethodWithParameter(param));
}

private void MethodWithParameter(string param)
{
    //Do stuff
}

編集

一般的な需要があるため、Task起動は呼び出しスレッドと並行して実行されることに注意する必要があります。デフォルトを想定すると、TaskSchedulerこれは.NETを使用しますThreadPool。とにかく、これはTask、一度に複数のスレッドによってアクセスされる可能性があるとして渡されるパラメータを考慮し、それらを共有状態にする必要があることを意味します。これには、呼び出し元のスレッドでそれらにアクセスすることも含まれます。

私の上記のコードでは、そのケースは完全に議論の余地があります。文字列は不変です。だから私はそれらを例として使用しました。しかし、あなたが使用していないと言うString...

一つの解決策は、使用することですasyncawait。これは、デフォルトSynchronizationContextで、呼び出し元のスレッドをキャプチャし、の呼び出し後にメソッドの残りの部分の継続を作成awaitし、作成されたにアタッチしTaskます。このメソッドがWinFormsGUIスレッドで実行されている場合は、次のタイプになります。WindowsFormsSynchronizationContext

継続は、キャプチャにポストバックされた後に実行SynchronizationContextされます-これもデフォルトでのみです。したがって、await呼び出し後に開始したスレッドに戻ります。これは、特にを使用して、さまざまな方法で変更できますConfigureAwait。要するに、そのメソッドの残りの部分はまで続行されませんTask、別のスレッドで完了しました。ただし、呼び出し元のスレッドは、メソッドの残りの部分ではなく、引き続き並行して実行されます。

メソッドの残りの実行が完了するのを待つことは、望ましい場合と望ましくない場合があります。そのメソッドの何も後で渡されたパラメータにアクセスしTaskない場合はawait、まったく使用したくない場合があります。

または、メソッドの後半でこれらのパラメータを使用する場合もあります。await安全に作業を続けることができるので、すぐに行う理由はありません。同じメソッドでも、Task返された変数をawait後で変数に格納できることを忘れないでください。たとえば、他の作業を行った後、渡されたパラメータに安全にアクセスする必要がある場合などです。ここでも、あなたはないではないに必要awaitTask実行するとき右側に配置する。

とにかく、渡されたパラメータに関してこのスレッドセーフにする簡単な方法は、これTask.Runを行うことです。

最初飾る必要がありますRunAsyncasync

private async void RunAsync()

重要な注意点

リンクされたドキュメントに記載されているように、マークされたメソッドはvoidを返さないことが望ましいです。これに対する一般的な例外は、ボタンクリックなどのイベントハンドラーです。それらはvoidを返さなければなりません。それ以外の場合は、を使用するときに常にまたはを返そうとします。かなりの数の理由から、これは良い習慣です。async TaskTask<TResult>async

これでawaitTask以下のようなものを実行できます。awaitなしでは使用できませんasync

await Task.Run(() => MethodWithParameter(param));
//Code here and below in the same method will not run until AFTER the above task has completed in one fashion or another

したがって、一般にawait、タスクを実行する場合、渡されたパラメーターを潜在的に共有されるリソースとして扱うことを回避でき、複数のスレッドから何かを一度に変更することのすべての落とし穴があります。また、クロージャに注意してください。それらについては詳しく説明しませんが、リンクされた記事はそれをうまくやってくれます。

サイドノート

少し外れたトピックですが、WinForms GUIスレッドでは、でマークされているため、あらゆるタイプの「ブロック」を使用する場合は注意が必要[STAThread]です。を使用してawaitもまったくブロックされませんが、ある種のブロックと組み合わせて使用​​されることがあります。

WinForms GUIスレッドを技術的にブロックできないため、「ブロック」は引用符で囲んでいます。はい、あなたが使用している場合lockWinFormsのGUI上で、それはスレッドまだそれがの「ブロック」を考え、あなたにもかかわらず、メッセージをポンプ。そうではありません。

これは、非常にまれなケースで奇妙な問題を引き起こす可能性があります。lockたとえば、ペイントするときにを使用したくない理由の1つ。しかし、それはフリンジで複雑なケースです。しかし、私はそれがクレイジーな問題を引き起こすのを見てきました。だから私は完全を期すためにそれを書き留めました。


23
あなたは待っていませんTask.Run(() => MethodWithParameter(param));。つまり、のparamに変更され場合、Task.Runで予期しない結果が生じる可能性がありMethodWithParameterます。
アレクサンドルセヴェリーノ2015年

8
それが間違っているのに、なぜこれが受け入れられた答えなのですか。これは、状態オブジェクトを渡すこととまったく同じではありません。
Egor Pavlikhin 2017

7
@ Zer0状態オブジェクトは、Task.Factory.StartNew msdn.microsoft.com/en-us/library/dd321456(v=vs.110).aspxの2番目のパラメーターであり、その時点でのオブジェクトの値を保存します。 StartNewを呼び出すと、回答によってクロージャーが作成され、参照が保持されます(タスクの実行前にparamの値が変更されると、タスクでも変更されます)。そのため、コードは質問の内容とまったく同じではありません。 。答えは、Task.Run()でそれを書く方法がないということです。
Egor Pavlikhin 2017

3
@ Zer0おそらくあなたはソースコードを読むべきです。1つは状態オブジェクトを渡し、もう1つは渡しません。それは私が最初から言ったことです。Task.Runは、Task.Factory.StartNewの省略形ではありません。状態オブジェクトのバージョンはレガシーの理由で存在しますが、それでも存在し、動作が異なる場合があるため、人々はそれを認識しておく必要があります。
Egor Pavlikhin 2017年

3
Toubの記事を読んで、この文を強調します。「オブジェクトの状態を受け入れるオーバーロードを使用できます。これは、パフォーマンスに敏感なコードパスの場合、クロージャとそれに対応する割り当てを回避するために使用できます」。これは、@ ZeroがTask.RunoverStartNewの使用法を検討するときに意味していることだと思います。
davidcarr

34

変数キャプチャを使用して、パラメータを「渡し」ます。

var x = rawData;
Task.Run(() =>
{
    // Do something with 'x'
});

rawData直接使用することもできますrawDataが、タスクの外部の値を変更すると(たとえば、forループ内のイテレーター)、タスクの内部の値も変更されるので注意が必要です。


11
を呼び出しTask.Runた直後に変数が変更される可能性があるという重要な事実を考慮に入れるための+1 。
アレクサンドルセヴェリーノ2015年

1
これはどのように役立ちますか?タスクスレッド内でxを使用し、xがオブジェクトへの参照であり、タスクスレッドの実行と同時にオブジェクトが変更された場合、大混乱につながる可能性があります。
Ovi 2016

1
@ Ovi-WanKenobiはい、しかしそれはこの質問の内容ではありません。それはパラメータを渡す方法でした。オブジェクトへの参照をパラメータとして通常の関数に渡した場合、そこでもまったく同じ問題が発生します。
スコットチェンバレン

うん、これは動作しません。私のタスクには、呼び出し元のスレッドでxへの参照がありません。私はただnullになります。
デビッドプライス

スコット・チェンバレン、キャプチャを介して議論を渡すことには、独自の問題が伴います。特に、メモリリークとメモリプレッシャーの問題があります。特にスケールアップしようとするとき。(詳細については、「メモリリークを引き起こす可能性のある8つの方法」を参照してください)。
RashadRivera

7

今からあなたもすることができます:

Action<int> action = (o) => Thread.Sleep(o);
int param = 10;
await new TaskFactory().StartNew(action, param)

これは、状態を渡すことができ、Kaden Burgartの回答で言及されている可能性のある状況を防ぐため、最良の回答です。たとえばIDisposable、ReSharperの警告「キャプチャされた変数は外部スコープに配置されています」を解決するためにオブジェクトをタスクデリゲートに渡す必要がある場合、これは非常にうまく機能します。一般に信じられていることとは反対に、状態を渡す必要Task.Factory.StartNewがあるTask.Run場所の代わりに使用しても問題はありません。こちらをご覧ください
ネオ

7

これが古いスレッドであることは知っていますが、承認された投稿にまだ問題があるため、使用しなければならなくなった解決策を共有したいと思いました。

問題:

Alexandre Severinoが指摘しているように、param(以下の関数で)関数呼び出しの直後に変更すると、で予期しない動作が発生する可能性がありますMethodWithParameter

Task.Run(() => MethodWithParameter(param)); 

私の解決策:

これを説明するために、私は次のコード行のようなものを書くことになりました。

(new Func<T, Task>(async (p) => await Task.Run(() => MethodWithParam(p)))).Invoke(param);

これにより、タスクの開始後にパラメーターが非常に迅速に変更されたにもかかわらず(投稿されたソリューションで問題が発生した)、パラメーターを非同期で安全に使用できました。

このアプローチを使用すると、param(値型)はその値を渡されるため、param変更後にasyncメソッドが実行された場合pでもparam、このコード行が実行されたときの値はすべて保持されます。


5
これをより読みやすく、より少ないオーバーヘッドで行う方法を考えられる人を心待ちにしています。これは確かにかなり醜いです。
Kaden Burgart 2016年

5
どうぞ:var localParam = param; await Task.Run(() => MethodWithParam(localParam));
Stephen Cleary 2016年

1
ちなみに、スティーブンは1年半前にすでに彼の答えで議論しました。
Servy 2016年

1
@Servy:それがスコットの答えでした。私はこれに答えませんでした。
スティーブンクリアリー2016年

私はこれをforループで実行していたので、スコットの答えは実際にはうまくいきませんでした。ローカルパラメータは、次の反復でリセットされます。私が投稿した回答の違いは、paramがラムダ式のスコープにコピーされるため、変数がすぐに安全になることです。スコットの答えでは、パラメーターはまだ同じスコープ内にあるため、回線の呼び出しと非同期関数の実行の間で変更される可能性があります。
Kaden Burgart 2016年

5

Task.Runを使用するだけです

var task = Task.Run(() =>
{
    //this will already share scope with rawData, no need to use a placeholder
});

または、メソッドで使用して後でタスクを待つ場合

public Task<T> SomethingAsync<T>()
{
    var task = Task.Run(() =>
    {
        //presumably do something which takes a few ms here
        //this will share scope with any passed parameters in the method
        return default(T);
    });

    return task;
}

1
そのようにすると、OPのStartNewの例のように渡さfor(int rawData = 0; rawData < 10; ++rawData) { Task.Run(() => { Console.WriteLine(rawData); } ) }れた場合と同じように動作しない場合は、クロージャに注意してくださいrawData
スコットチェンバレン

@ ScottChamberlain-それは別の例のようです;)ラムダ値を閉じることについてほとんどの人が理解してくれることを願っています。
Travis J

3
そして、これらの以前のコメントが意味をなさない場合は、トピックに関するEric Lipperのブログを参照してください:blogs.msdn.com/b/ericlippert/archive/2009/11/12/…これが非常にうまくいく理由を説明しています。
Travis J

2

元の問題が私が抱えていたのと同じ問題であったかどうかは不明です。イテレーターの値を保持し、インラインを維持しながらループ内の計算でCPUスレッドを最大化して、大量の変数がワーカー関数に渡されないようにしたいのです。

for (int i = 0; i < 300; i++)
{
    Task.Run(() => {
        var x = ComputeStuff(datavector, i); // value of i was incorrect
        var y = ComputeMoreStuff(x);
        // ...
    });
}

外側のイテレータを変更し、その値をゲートでローカライズすることで、これを機能させることができました。

for (int ii = 0; ii < 300; ii++)
{
    System.Threading.CountdownEvent handoff = new System.Threading.CountdownEvent(1);
    Task.Run(() => {
        int i = ii;
        handoff.Signal();

        var x = ComputeStuff(datavector, i);
        var y = ComputeMoreStuff(x);
        // ...

    });
    handoff.Wait();
}

0

アイデアは、上記のようなシグナルの使用を避けることです。int値を構造体にポンピングすると、それらの値が(構造体で)変更されるのを防ぎます。次の問題がありました:DoSomething(i)が呼び出される前にループ変数iが変更されます(()=> DoSomething(i、i i)が呼び出される前にループの終わりでiがインクリメントされました)。構造体では、それはもう起こりません。見つけるのが難しいバグ:DoSomething(i、i i)は見栄えが良いですが、iの値が異なるたびに(またはi = 100の場合は100回だけ)呼び出されるかどうかはわかりません。したがって、-> struct

struct Job { public int P1; public int P2; }
…
for (int i = 0; i < 100; i++) {
    var job = new Job { P1 = i, P2 = i * i}; // structs immutable...
    Task.Run(() => DoSomething(job));
}

1
これは質問に答えるかもしれませんが、レビューのためにフラグが立てられました。説明のない回答は、しばしば低品質と見なされます。これが正解である理由について、いくつかの解説を提供してください。
ダン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.