これを行う最も簡単な方法は、呼び出しを Task.Run にダンプし、Task.Run が返されるのを待つことです。これにより、集中的な作業が別のスレッドにオフロードされ、UI スレッドが続行できるようになります。以下は、時間のかかる別のメソッドが戻る前に待機する必要があるメソッドの簡単な例です。
static void Main()
{
Console.WriteLine("Begin");
var result = BlockingMethod("Hi!");
Console.WriteLine("Result: " + result);
Console.ReadLine();
}
static bool BlockingMethod(string someText)
{
Thread.Sleep(2000);
return someText.Contains("SomeOtherText");
}
ご覧のとおり、BlockingMethod メソッドには、最初のステートメントとして Thread.Sleep(2000) があります。これは、呼び出し元のメソッド Main を実行しているスレッドが、BlockingMethod の結果を取得する前に 2 秒間待機する必要があることを意味します。Main メソッドを実行しているスレッドが UI の再描画を処理している場合、これは UI が完全に 2 秒間応答しない/ロックされているように見えることを意味します。次のように、BlockingMethod を待機する作業を別のスレッドにオフロードできます。
まず、呼び出し元のメソッドを「async」としてマークします。これは、非同期メソッドのすべての「await」に対してステート マシンに似たものを生成するようコンパイラに指示するためです。
static async void Main()
{
Console.WriteLine("Begin");
var result = BlockingMethod("Hi!");
Console.WriteLine("Result: " + result);
Console.ReadLine();
}
次に、戻り型 'void' を Task に変更します。これにより、他のスレッドが待機できるようになります (気にしない場合は async void のままにしておくことができますが、戻り値の型を持つ非同期メソッドは Task を返す必要があります)。
static async Task Main()
{
Console.WriteLine("Begin");
var result = BlockingMethod("Hi!");
Console.WriteLine("Result: " + result);
Console.ReadLine();
}
これで、メソッドを非同期で実行できるようになり、他のメソッドが待機できるようになりました。しかし、まだブロック メソッドを同期的に待機しているため、これで問題は解決していません。そのため、Task.Run を呼び出してその結果を待つことにより、ブロッキング メソッドを別のスレッドに移動します。
static async Task Main()
{
Console.WriteLine("Begin");
await Task.Run(() => BlockingMethod("Hi!"));
Console.WriteLine("Result: " + result);
Console.ReadLine();
}
しかし、これには問題があります。ブロッキング メソッドは後でメソッドで使用したいものを返していましたが、Task.Run は void を返します。他のスレッドで実行されているブロッキング メソッドによって返された変数にアクセスするには、クロージャでローカル変数をキャプチャする必要があります。
static async Task Main()
{
Console.WriteLine("Begin");
bool result = true;
await Task.Run(() => result = BlockingMethod("Hi!"));
Console.WriteLine("Result: " + result);
Console.ReadLine();
}
つまり、要約すると、通常の同期メソッド (書き換えできないサードパーティ API ではなく、書き換え可能な呼び出しメソッド) を使用して、非同期としてマークされるように変更し、タスクを返しました。結果を返すようにしたい場合は、一般的に型指定された Task である必要があります。その後、ブロッキング メソッドの呼び出しを Task.Run でラップし (ブロッキング メソッドを実行するための新しいスレッドを作成します)、実行する Action (ラムダ構文) を指定しました。そのアクションでは、ブロッキング メソッドの結果をキャプチャできる呼び出し関数 (これがクロージャ) で定義された変数を参照しました。
ここで、別の場所で同期のブロッキング メソッドを非同期的に待機しています。ブロッキング メソッドが本質的に非同期でなくても問題ありません。別のスレッドで同期的に実行し、スレッドがその結果を待つだけであるためです。
これのいずれかが不明な場合は、コメントしてください。非同期性は最初は少し混乱しますが、レスポンシブ UI にとっては天の恵みです。
編集:
Scott Chamberlain のコメントに応えて、Task.Run には、実行中のメソッドの型を返すオーバーロードもあります (Action ではなく Func を指定します)。したがって、次のように簡単に使用できます。
static async Task MainAsync()
{
Console.WriteLine("Begin");
bool result = await Task.Run(() => BlockingMethod("Hi!"));
Console.WriteLine("Result: " + result);
Console.ReadLine();
}
Scott Chamberlain が指摘しているように、別のスレッドで作業を実行しても、固有のパフォーマンス上の利点はないことに注意してください。多くの場合、スレッドのセットアップと破棄にはコストがかかるため、実際にはパフォーマンスが低下する可能性があります。タスクベースの非同期は、使用中のスレッド (UI スレッドなど) をブロックしないようにしてリクエストに応答できるようにする場合、または Parallel.ForEach などを使用して作業を適切に分割する場合にのみ役立ちます。