おお、これは面白そうですね。私はまだ CTP をいじっているのではなく、ホワイトペーパーを確認しているだけです。それについての Anders Hejlsberg の講演を見た後、私はそれがどのように有用であるかを理解できると思います。
私が理解しているように、async を使用すると、非同期呼び出しの記述が読みやすく実装しやすくなります。これとほぼ同じように、イテレータを記述することは今の方が簡単です (機能を手で書き出すのとは対照的です)。ブロックが解除されるまで、有用な作業を行うことができないため、これは必須のブロック プロセスです。ファイルをダウンロードしていた場合、そのファイルを取得してスレッドを無駄にするまでは、何の役にも立ちません。未定の長さでブロックし、何らかの結果を返すことがわかっている関数を呼び出して、それを処理する方法を考えてみてください (たとえば、結果をファイルに保存します)。あなたはそれをどのように書きますか?簡単な例を次に示します。
static object DoSomeBlockingOperation(object args)
{
// block for 5 minutes
Thread.Sleep(5 * 60 * 1000);
return args;
}
static void ProcessTheResult(object result)
{
Console.WriteLine(result);
}
static void CalculateAndProcess(object args)
{
// let's calculate! (synchronously)
object result = DoSomeBlockingOperation(args);
// let's process!
ProcessTheResult(result);
}
わかりました、実装しました。しかし、待ってください。計算が完了するまでに数分かかります。インタラクティブなアプリケーションが必要で、計算が行われている間 (UI のレンダリングなど) 他のことをしたい場合はどうすればよいでしょうか? 関数を同期的に呼び出し、スレッドがブロック解除されるのを待っているため、関数がアプリケーションを効果的にフリーズするのを待つ必要があるため、これは良くありません。
答え、関数の高価な関数を非同期で呼び出します。そうすれば、ブロッキング操作が完了するのを待つ必要がなくなります。しかし、どうやってそれを行うのですか?関数を非同期で呼び出し、ブロックが解除されたときに呼び出されるコールバック関数を登録して、結果を処理できるようにします。
static void CalculateAndProcessAsyncOld(object args)
{
// obtain a delegate to call asynchronously
Func<object, object> calculate = DoSomeBlockingOperation;
// define the callback when the call completes so we can process afterwards
AsyncCallback cb = ar =>
{
Func<object, object> calc = (Func<object, object>)ar.AsyncState;
object result = calc.EndInvoke(ar);
// let's process!
ProcessTheResult(result);
};
// let's calculate! (asynchronously)
calculate.BeginInvoke(args, cb, calculate);
}
- 注: もちろん、これを行うために別のスレッドを開始することもできますが、それは、ブロックが解除されるのを待っているだけのスレッドを生成してから、いくつかの有用な作業を行うことを意味します。それはもったいないでしょう。
これで、呼び出しは非同期になり、計算が完了して処理されるのを待つ必要がなくなりました。非同期で行われます。できたら終わります。コードを非同期で直接呼び出す代わりに、Task を使用できます。
static void CalculateAndProcessAsyncTask(object args)
{
// create a task
Task<object> task = new Task<object>(DoSomeBlockingOperation, args);
// define the callback when the call completes so we can process afterwards
task.ContinueWith(t =>
{
// let's process!
ProcessTheResult(t.Result);
});
// let's calculate! (asynchronously)
task.Start();
}
これで、関数を非同期で呼び出しました。しかし、そのようになるのに何が必要でしたか?まず、デリゲート/タスクを非同期で呼び出すことができるようにする必要がありました。結果を処理してから関数を呼び出すことができるコールバック関数が必要でした。非同期で何かを呼び出すためだけに、2 行の関数呼び出しをさらに多くの機能に変更しました。それだけでなく、コード内のロジックは以前よりも複雑になっています。タスクを使用することでプロセスが簡素化されましたが、それを実現するために何かを行う必要がありました。非同期で実行して結果を処理したいだけです。なぜそれができないのですか?さて、次のことができます。
// need to have an asynchronous version
static async Task<object> DoSomeBlockingOperationAsync(object args)
{
//it is my understanding that async will take this method and convert it to a task automatically
return DoSomeBlockingOperation(args);
}
static async void CalculateAndProcessAsyncNew(object args)
{
// let's calculate! (asynchronously)
object result = await DoSomeBlockingOperationAsync(args);
// let's process!
ProcessTheResult(result);
}
これは、単純な操作 (計算、処理) を使用した非常に単純化された例です。各操作を便利に個別の関数に入れることができず、代わりに数百行のコードがあると想像してみてください。これは、非同期呼び出しの利点を得るために、さらに複雑になります。
ホワイトペーパーで使用されているもう 1 つの実用的な例は、UI アプリでの使用です。上記の例を使用するように変更します。
private async void doCalculation_Click(object sender, RoutedEventArgs e) {
doCalculation.IsEnabled = false;
await DoSomeBlockingOperationAsync(GetArgs());
doCalculation.IsEnabled = true;
}
UI プログラミング (WinForms であれ WPF であれ) を行ったことがあり、ハンドラー内で高価な関数を呼び出そうとしたことがあれば、これが便利であることがわかるでしょう。これにバックグラウンド ワーカーを使用しても、バックグラウンド スレッドが機能するまで待機しているため、あまり役に立ちません。
プリンターなどの外部デバイスを制御する方法があるとします。また、障害後にデバイスを再起動したいと考えていました。当然、プリンターが起動して操作できるようになるまでには時間がかかります。再起動が役に立たないことを考慮して、再起動を試みる必要がある場合があります。それを待つしかありません。非同期で行った場合ではありません。
static async void RestartPrinter()
{
Printer printer = GetPrinter();
do
{
printer.Restart();
printer = await printer.WaitUntilReadyAsync();
} while (printer.HasFailed);
}
非同期なしでループを書くことを想像してみてください。
私が持っている最後の例。関数で複数のブロック操作を行う必要があり、非同期で呼び出したい場合を想像してください。何がいいですか?
static void DoOperationsAsyncOld()
{
Task op1 = new Task(DoOperation1Async);
op1.ContinueWith(t1 =>
{
Task op2 = new Task(DoOperation2Async);
op2.ContinueWith(t2 =>
{
Task op3 = new Task(DoOperation3Async);
op3.ContinueWith(t3 =>
{
DoQuickOperation();
}
op3.Start();
}
op2.Start();
}
op1.Start();
}
static async void DoOperationsAsyncNew()
{
await DoOperation1Async();
await DoOperation2Async();
await DoOperation3Async();
DoQuickOperation();
}
ホワイトペーパーを読んでください。実際には、並列タスクの作成などの実用的な例がたくさんあります。
CTP で、または .NET 5.0 が最終的に完成したときに、これを試してみるのが待ちきれません。