バックグラウンドタスクで実行したい処理が長時間実行されています。タスクの終わりに、私はそれが完了したことを知らせたいと思います。そのため、基本的に、バックグラウンドで実行したい非同期タスクが2つあります。
私は継続でこれをしています、しかし継続は最初のタスクが完了する前に始まっています。予想される動作は、最初のタスクが完了した後にのみ継続が実行されることです。
これが問題を示すサンプルコードです。
// Setup task and continuation
var task = new Task(async () =>
{
DebugLog("TASK Starting");
await Task.Delay(1000); // actual work here
DebugLog("TASK Finishing");
});
task.ContinueWith(async (x) =>
{
DebugLog("CONTINUATION Starting");
await Task.Delay(100); // actual work here
DebugLog("CONTINUATION Ending");
});
task.Start();
DebugLog関数:
static void DebugLog(string s, params object[] args)
{
string tmp = string.Format(s, args);
System.Diagnostics.Debug.WriteLine("{0}: {1}", DateTime.Now.ToString("HH:mm:ss.ffff"), tmp);
}
期待される出力:
TASK Starting
TASK Finishing
CONTINUATION Starting
CONTINUATION Ending
実績:
TASK Starting
CONTINUATION Starting
CONTINUATION Ending
TASK Finishing
繰り返しますが、私の質問は、最初のタスクが完了する前に継続が開始されるのはなぜですか?最初のタスクが完了した後にのみ継続を実行するにはどうすればよいですか。
回避策1
最初のタスクを同期させると上記のコードを期待通りに動作させることができます。Wait
にTask.Delay
そのようです:
var task = new Task(() =>
{
DebugLog("TASK Starting");
Task.Delay(1000).Wait(); // Wait instead of await
DebugLog("TASK Finishing");
});
多くの理由で、使うのは悪いことですWait
このような。その理由の1つは、スレッドがブロックされていることです。これは避けたいものです。
回避策2
私がタスクの作成を受けて、それをそれ自身の機能に移動させるならば、それは同様にうまくいくようです:
// START task and setup continuation
var task = Test1();
task.ContinueWith(async (x) =>
{
DebugLog("CONTINUATION Starting");
await Task.Delay(100); // actual work here
DebugLog("CONTINUATION Ending");
});
static public async Task Test1()
{
DebugLog("TASK Starting");
await Task.Delay(1000); // actual work here
DebugLog("TASK Finishing");
}
上記のアプローチの功績は、この幾分関連した(しかし重複してはいない)質問にあります。Task.ContinueWithで非同期コールバックを使用する
回避策#2は回避策#1よりも優れており、上記の最初のコードが機能しない理由について説明がない場合は、おそらく私が採用するアプローチです。
元のコードは機能していません。async
ラムダはに翻訳されていますasync void
その下のメソッド。他のコードに完了したことを通知するための組み込みの方法はありません。だから、あなたが見ているのはその違いです。async void
そしてasync Task
。これは避けるべき理由の一つです。async void
。
とは言っても、あなたのコードでできることなら、Task.Run
の代わりにTask
コンストラクタとStart
;そして使うawait
のではなくContinueWith
:
var task = Task.Run(async () =>
{
DebugLog("TASK Starting");
await Task.Delay(1000); // actual work here
DebugLog("TASK Finishing");
});
await task;
DebugLog("CONTINUATION Starting");
await Task.Delay(100); // actual work here
DebugLog("CONTINUATION Ending");
Task.Run
そしてawait
より自然に働くasync
コード。
特に非同期タスクを開始するときは、タスクを直接開始するためにTaskコンストラクターを使用しないでください。バックグラウンドで実行される作業をオフロードしたい場合Task.Run
代わりに:
var task = Task.Run(async () =>
{
DebugLog("TASK Starting");
await Task.Delay(1000); // actual work here
DebugLog("TASK Finishing");
});
継続については、ラムダ式の最後にそのロジックを追加するだけの方が良いでしょう。しかし、もしあなたが慣れているのならContinueWith
あなたが使用する必要がありますUnwrap
実際の非同期タスクを取得して保存し、例外を処理できるようにします。
task = task.ContinueWith(async (x) =>
{
DebugLog("CONTINUATION Starting");
await Task.Delay(100); // actual work here
DebugLog("CONTINUATION Ending");
}).Unwrap();
try
{
await task;
}
catch
{
// handle exceptions
}
コードをに変更します。
// Setup task and continuation
var t1 = new Task(() =>
{
Console.WriteLine("TASK Starting");
Task.Delay(1000).Wait(); // actual work here
Console.WriteLine("TASK Finishing");
});
var t2 = t1.ContinueWith((x) =>
{
Console.WriteLine("C1");
Task.Delay(100).Wait(); // actual work here
Console.WriteLine("C2");
});
t1.Start();
// Exception will be swallow without the line below
await Task.WhenAll(t1, t2);
Unwrap()
必要に応じてメソッド。の創造async void
ラムダは確かに望まれていませんが、また使用しないことの副作用以上の何ものでもありませんnew Task<Task>(...)
そうUnwrap()
うまくいくでしょう。 - Sam Harwellnew Task<Task>
そしてUnwrap
;しかし、新しいコードはうまくいくでしょうなぜならそれを避けるだろうasync void
ラムダ。それでもまだそれほど良いとは言えないでしょう。Task.Run
ベースのソリューション。 - Stephen ClearyTask.Run
そして元の続き。私の既存のコードがなぜうまくいかないのかについての洞察をありがとうございます。async void
。あなたがそれを言ったときもちろんもちろんすべてがもっとずっと理にかなっていました。それではなぜ私は継続を使用しているのですか?さて、ここで説明しようとするのはあまりにも多くのことに関係がありますがこの質問。 - chue x