web-dev-qa-db-ja.com

別のスレッドで非同期関数を実行する

非同期CTPを評価しています。

別のスレッドプールのスレッドで非同期関数の実行を開始するにはどうすればよいですか?

static async Task Test()
{
    // Do something, await something
}

static void Main( string[] args )
{
    // Is there more elegant way to write the line below?
    var t = TaskEx.Run( () => Test().Wait() );

    // Doing much more in this same thread
    t.Wait(); // Waiting for much more then just this single task, this is just an example
}
17
Soonts

私はStackOverflowを初めて使用しますが(私の処女の投稿)​​、Microsoftで作業しているチームに所属しているので、非同期CTPについて質問されていることに驚いています:)

私はあなたが何を目指しているのか理解していると思います。あなたをそこに導くためにあなたが正しくやっていることがいくつかあります。

私があなたが望むと思うもの:

_static async Task Test()
{
    // Do something, await something
}

static void Main(string[] args)
{
    // In the CTP, use Task.RunEx(...) to run an Async Method or Async Lambda
    // on the .NET thread pool
    var t = TaskEx.RunEx(Test);
    // the above was just shorthand for
    var t = TaskEx.RunEx(new Func<Task>(Test));
    // because the C# auto-wraps methods into delegates for you.

    // Doing much more in this same thread
    t.Wait(); // Waiting for much more then just this single task, this is just an example
}
_

Task.RunとTask.RunEx

このCTPは.NET4.0の上にインストールされるため、mscorlibのactual _System.Threading.Tasks.Task_タイプにパッチを適用したくありませんでした。代わりに、プレイグラウンドAPIは、競合する場合はFooExという名前になります。

なぜそれらのいくつかにRun(...)RunEx(...)の名前を付けたのですか?その理由は、CTPをリリースするまでにまだ完了していなかった、メソッドのオーバーロードの再設計によるものです。現在機能しているコードベースでは、C#メソッドのオーバーロードルールを少し調整して、非同期ラムダで正しいことが行われるようにする必要がありました。これにより、voidTask、または_Task<T>_。

問題は、非同期メソッドまたはラムダがTaskまたは_Task<T>_を返す場合、タスクはタスクの一部として自動的に生成されるため、実際には戻り式に外部タスクタイプがないことです。メソッドまたはラムダの呼び出し。通常、returnステートメントの式はメソッドまたはラムダの戻り値の型に直接変換できるため、これはコードを明確にするための適切なエクスペリエンスを強く望んでいますが、以前はまったく異なっていました。

したがって、非同期voidラムダと非同期Taskラムダの両方が引数なしで_return;_をサポートします。したがって、どちらを選択するかを決定するために、メソッドのオーバーロード解決を明確にする必要があります。したがって、Run(...)とRunEx(...)の両方を使用する唯一の理由は、PDC 2010ヒット。


非同期メソッド/ラムダの考え方

これが混乱のポイントであるかどうかはわかりませんが、私はそれについて言及したいと思いました-非同期メソッドまたは非同期ラムダを書いているとき、それを呼び出す人の特定の特性を帯びることがあります。これは2つのことを前提としています。

  • あなたが待っているタイプ
  • そしておそらく同期コンテキスト(上記に応じて)

待機用のCTP設計と現在の内部設計はどちらも非常にパターンベースであるため、APIプロバイダーは、「待機」できる活気に満ちた一連の機能を具体化するのに役立ちます。これは、待機しているタイプによって異なる可能性があり、その一般的なタイプはTaskです。

Taskのawait実装は非常に合理的であり、現在のスレッドのSynchronizationContextに従って、作業を延期する方法を決定します。すでにWinFormsまたはWPFメッセージループに入っている場合、延期された実行は同じメッセージループに戻ります(BeginInvoke()「メソッドの残りの部分」を使用したかのように)。タスクを待っていて、すでに.NETスレッドプールを使用している場合、「メソッドの残りの部分」はスレッドプールスレッドの1つで再開されます(ただし、必ずしも同じスレッドである必要はありません)。ほとんどの場合、最初に利用可能なプールスレッドを使用して満足しています。


Wait()メソッドの使用に関する注意

使用したサンプル:var t = TaskEx.Run( () => Test().Wait() );

それは何ですか:

  1. 周囲のスレッドで、TaskEx.Run(...)を同期的に呼び出して、スレッドプールでラムダを実行します。
  2. ラムダにはスレッドプールスレッドが指定されており、非同期メソッドを呼び出します。
  3. 非同期メソッドTest()はラムダから呼び出されます。ラムダはスレッドプールで実行されていたため、Test()内の継続は、スレッドプール内の任意のスレッドで実行できます。
  4. ラムダは、待機がなかったため、実際にはそのスレッドのスタックを空にしません。この場合のTPLの動作は、Wait()呼び出しの前にTest()が実際に終了したかどうかによって異なります。ただし、この場合、Test()が別のスレッドで実行を終了するのを待機している間、スレッドプールスレッドをブロックする可能性があります。

これが「await」演算子の主な利点です。元のスレッドをブロックすることなく、後で実行するコードを追加できることです。スレッドプールの場合、スレッドの使用率を向上させることができます。

VBまたはC#の非同期CTPについて他に質問がある場合は、ぜひお知らせください:)

51
Theo Yaung

Taskを返すメソッドは、他の何かに便乗するのではなく、真に新しい作業を開始するかどうかを判断するのが通常です。

この場合、あなたのようには見えません本当にTest()メソッドを非同期にしたい-少なくとも、あなたはそうではありませんsing非同期です。別のスレッドで作業を開始しているだけです...Test()メソッドは完全に同期している可能性があり、次のように使用できます。

Task task = TaskEx.Run(Test);
// Do stuff
t.Wait();

これには、非同期CTPの良さは必要ありません。

5
Jon Skeet

これがコンソールアプリケーションでなければ、あるでしょう。たとえば、Windowsフォームアプリケーションでこれを行う場合、次のことができます。

// Added to a button click event, for example
public async void button1_Click(object sender, EventArgs e)
{
    // Do some stuff
    await Test();
    // Do some more stuff
}

ただし、コンソールにはデフォルトのSynchronizationContextがないため、期待どおりに機能しません。コンソールアプリケーションでは、タスクを明示的に取得して、最後に待機する必要があります。

これをWindowsフォーム、WPF、またはWCFサービスのUIスレッドで実行している場合は、結果を適切にマーシャリングするために使用される有効なSynchronizationContextがあります。ただし、コンソールアプリケーションでは、await呼び出しで制御が「返される」と、プログラムは続行され、すぐに終了します。これはすべてを台無しにし、予期しない動作を引き起こす傾向があります。

2
Reed Copsey