バックグラウンドタスクで実行したい処理が長時間実行されています。タスクの終わりに、私はそれが完了したことを知らせたいと思います。そのため、基本的に、バックグラウンドで実行したい非同期タスクが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