私はマルチタスクネットワークプロジェクトに取り組んでおり、Threading.Tasks
に新しいです。私は簡単なTask.Factory.StartNew()
を実装しましたが、どうすればTask.Run()
でできるのでしょうか?
基本的なコードは次のとおりです。
Task.Factory.StartNew(new Action<object>(
(x) =>
{
// Do something with 'x'
}), rawData);
Object BrowserでSystem.Threading.Tasks.Task
を調べましたが、Action<T>
のようなパラメーターが見つかりませんでした。 Action
パラメーターを取り、typeを持たないvoid
のみがあります。
類似するものが2つだけあります:static Task Run(Action action)
とstatic Task Run(Func<Task> function)
ですが、両方でパラメーターをポストすることはできません。
はい、簡単な拡張メソッドを作成できることはわかっていますが、私の主な質問は、それを1行で記述できるかどうか with Task.Run()
?
private void RunAsync()
{
string param = "Hi";
Task.Run(() => MethodWithParameter(param));
}
private void MethodWithParameter(string param)
{
//Do stuff
}
編集
一般的な需要により、起動されたTask
は呼び出しスレッドと並行して実行されることに注意する必要があります。デフォルトのTaskScheduler
を想定すると、これは.NET ThreadPool
を使用します。とにかく、これは、Task
に渡されるパラメーターを、複数のスレッドが同時にアクセスする可能性があるため、それらを共有状態にするために考慮する必要があることを意味します。これには、呼び出しスレッドでのアクセスが含まれます。
上記のコードでは、その場合は完全に無意味になります。文字列は不変です。それが私がそれらを例として使用した理由です。しかし、String
を使用していないと言います...
1つの解決策は、async
およびawait
を使用することです。これは、デフォルトで、呼び出しスレッドのSynchronizationContext
をキャプチャし、await
の呼び出し後にメソッドの残りの継続を作成し、作成されたTask
にアタッチします。このメソッドがWinForms GUIスレッドで実行されている場合、タイプはWindowsFormsSynchronizationContext
になります。
継続は、キャプチャされたSynchronizationContext
にポストバックされた後に実行されます-再びデフォルトでのみ。したがって、await
呼び出しの後に開始したスレッドに戻ります。これはさまざまな方法で、特に ConfigureAwait
を使用して変更できます。つまり、そのメソッドの残りの部分は、Task
が別のスレッドで完了するまでafterまで継続しません。ただし、呼び出しスレッドは、メソッドの残りの部分ではなく、並行して実行され続けます。
メソッドの残りの実行の完了を待機することは、望ましい場合と望ましくない場合があります。そのメソッドの何も後でTask
に渡されたパラメーターにアクセスしない場合は、await
をまったく使用したくない場合があります。
または、メソッドの後半でこれらのパラメーターを使用することもできます。安全に作業を続行できるため、すぐにawait
にする理由はありません。同じメソッドであっても、変数に返されたTask
を後で変数にawait
格納できます。たとえば、ひとたび他の作業を行った後、渡されたパラメーターに安全にアクセスする必要がある場合。繰り返しますが、実行するときにawait
でTask
をnotする必要があります。
とにかく、Task.Run
に渡されるパラメーターに関してこのスレッドセーフにする簡単な方法は、これを行うことです:
最初にRunAsync
をasync
で修飾する必要があります。
private async void RunAsync()
重要な注意事項
リンクされたドキュメントで言及されているように、 async
とマークされたメソッドはvoidを返さないことが望ましいです。これの一般的な例外は、ボタンクリックなどのイベントハンドラーです。それらはvoidを返さなければなりません。そうでない場合は、Task
を使用するときに、常にasync
またはTask<TResult>
を返そうとします。これにはいくつかの理由があります。
これで、以下のようにawait
を実行してTask
を実行できます。 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
すると、渡されたパラメーターを潜在的に共有されるリソースとして扱うことを回避できます。また、 closures に注意してください。これらについては詳しく説明しませんが、リンクされた記事で大いに役立ちます。
サイドノート
ちょっとしたトピックですが、[STAThread]
でマークされているため、WinForms GUIスレッドで「ブロッキング」を使用する場合は注意してください。 await
を使用してもまったくブロックされませんが、ある種のブロックと組み合わせて使用されることがあります。
技術的に WinForms GUIスレッドをブロックできない であるため、「ブロック」は引用符で囲まれています。はい、WinForms GUIスレッドでlock
を使用すると、「ブロック」されていると思われるにも関わらず、メッセージをポンプしますwill。そうではありません。
これは非常にまれなケースで奇妙な問題を引き起こす可能性があります。たとえば、ペイントするときにlock
を使用したくない理由の1つです。しかし、それはフリンジと複雑なケースです。しかし、私はそれがおかしな問題を引き起こすのを見てきました。そこで、完全を期すために書き留めました。
変数のキャプチャを使用してパラメーターを「渡す」。
var x = rawData;
Task.Run(() =>
{
// Do something with 'x'
});
rawData
を直接使用することもできますが、タスクの外部でrawData
の値を変更すると(たとえば、for
ループのイテレーター)、タスクの内部の値も変更されます。
私はこれが古いスレッドであることを知っていますが、受け入れられた投稿にはまだ問題があるので、使用しなければならなかったソリューションを共有したかったです。
問題:
Alexandre Severinoが指摘したように、関数呼び出しの直後にparam
(下の関数内)が変更されると、MethodWithParameter
で予期しない動作が発生する可能性があります。
Task.Run(() => MethodWithParameter(param));
私のソリューション:
これを説明するために、次のコード行のようなものを書くことになりました。
(new Func<T, Task>(async (p) => await Task.Run(() => MethodWithParam(p)))).Invoke(param);
これにより、タスクの開始後にパラメーターが非常に迅速に変更されたにもかかわらず(投稿されたソリューションで問題が発生したため)、パラメーターを非同期で安全に使用できました。
この方法を使用すると、param
(値型)に値が渡されるため、param
が変更された後に非同期メソッドが実行された場合でも、p
は、このコード行の実行時にparam
の値を持ちます。
今から次のこともできます:
Action<int> action = (o) => Thread.Sleep(o);
int param = 10;
await new TaskFactory().StartNew(action, param)
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;
}
元の問題が私と同じ問題であったかどうかは不明です:ループ内の計算で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();
}