以下は、非同期ソルブを使用した違いとさまざまな問題を説明するために最近使用したコードスニペットのシーケンスです。
GUIベースのアプリケーションに時間のかかるイベントハンドラーがあり、それを非同期にしたいとします。以下は、最初に使用する同期ロジックです。
while (true) {
string result = LoadNextItem().Result;
if (result.Contains("target")) {
Counter.Value = result.Length;
break;
}
}
LoadNextItemはTaskを返し、最終的には検査したい結果を生成します。現在の結果が探しているものである場合は、UIのカウンターの値を更新して、メソッドから戻ります。それ以外の場合は、LoadNextItemからの項目の処理を続行します。
非同期バージョンの最初のアイデア:継続を使用するだけです!とりあえずループ部分は無視しましょう。つまり、何がうまくいかないのでしょうか?
return LoadNextItem().ContinueWith(t => {
string result = t.Result;
if (result.Contains("target")) {
Counter.Value = result.Length;
}
});
これで、ブロックしないメソッドができました!代わりにクラッシュします。UIコントロールへの更新はすべてUIスレッドで行われるため、それを考慮する必要があります。ありがたいことに、継続をどのようにスケジュールするかを指定するオプションがあり、このためのデフォルトのオプションがあります。
return LoadNextItem().ContinueWith(t => {
string result = t.Result;
if (result.Contains("target")) {
Counter.Value = result.Length;
}
},
TaskScheduler.FromCurrentSynchronizationContext());
これで、クラッシュしないメソッドができました!代わりに静かに失敗します。継続はそれ自体が個別のタスクであり、そのステータスは先行タスクのステータスとは関係ありません。したがって、LoadNextItemが失敗した場合でも、呼び出し元には正常に完了したタスクのみが表示されます。さて、例外があれば渡してください:
return LoadNextItem().ContinueWith(t => {
if (t.Exception != null) {
throw t.Exception.InnerException;
}
string result = t.Result;
if (result.Contains("target")) {
Counter.Value = result.Length;
}
},
TaskScheduler.FromCurrentSynchronizationContext());
すばらしい、これは実際に機能します。単品の場合。では、そのループについてはどうでしょう。結局のところ、元の同期バージョンのロジックと同等のソリューションは次のようになります。
Task AsyncLoop() {
return AsyncLoopTask().ContinueWith(t =>
Counter.Value = t.Result,
TaskScheduler.FromCurrentSynchronizationContext());
}
Task<int> AsyncLoopTask() {
var tcs = new TaskCompletionSource<int>();
DoIteration(tcs);
return tcs.Task;
}
void DoIteration(TaskCompletionSource<int> tcs) {
LoadNextItem().ContinueWith(t => {
if (t.Exception != null) {
tcs.TrySetException(t.Exception.InnerException);
} else if (t.Result.Contains("target")) {
tcs.TrySetResult(t.Result.Length);
} else {
DoIteration(tcs);
}});
}
または、上記のすべての代わりに、asyncを使用して同じことを行うことができます。
async Task AsyncLoop() {
while (true) {
string result = await LoadNextItem();
if (result.Contains("target")) {
Counter.Value = result.Length;
break;
}
}
}
それは今ではずっといいですよね?
Wait
第二の例では、コールを、その後 2つのスニペットは、(主に)と同等になります。