web-dev-qa-db-ja.com

Task.Run with Parameter(s)?

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

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

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

Object BrowserSystem.Threading.Tasks.Taskを調べましたが、Action<T>のようなパラメーターが見つかりませんでした。 Actionパラメーターを取り、typeを持たないvoidのみがあります。

類似するものが2つだけあります:static Task Run(Action action)static Task Run(Func<Task> function)ですが、両方でパラメーターをポストすることはできません。

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

63
MFatihMAR
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格納できます。たとえば、ひとたび他の作業を行った後、渡されたパラメーターに安全にアクセスする必要がある場合。繰り返しますが、実行するときにawaitTasknotする必要があります。

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

最初にRunAsyncasyncで修飾する必要があります。

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つです。しかし、それはフリンジと複雑なケースです。しかし、私はそれがおかしな問題を引き起こすのを見てきました。そこで、完全を期すために書き留めました。

77
Zer0

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

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

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

25

私はこれが古いスレッドであることを知っていますが、受け入れられた投稿にはまだ問題があるので、使用しなければならなかったソリューションを共有したかったです。

問題:

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の値を持ちます。

5
Kaden Burgart

今から次のこともできます:

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

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;
}
3
Travis J

元の問題が私と同じ問題であったかどうかは不明です:ループ内の計算で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();
}
1
Harald J