0

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

最初のタスクを同期させると上記のコードを期待通りに動作させることができます。WaitTask.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よりも優れており、上記の最初のコードが機能しない理由について説明がない場合は、おそらく私が採用するアプローチです。

3 답변


4

元のコードは機能していません。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コード。


  • -1:元のコードは使用しないため機能していません。Unwrap()必要に応じてメソッド。の創造async voidラムダは確かに望まれていませんが、また使用しないことの副作用以上の何ものでもありませんnew Task<Task>(...)そうUnwrap()うまくいくでしょう。 - Sam Harwell
  • @ 280Z28:はい、元のコードをハッキングする可能性がありますnew Task<Task>そしてUnwrap;しかし、新しいコードはうまくいくでしょうなぜならそれを避けるだろうasync voidラムダ。それでもまだそれほど良いとは言えないでしょう。Task.Runベースのソリューション。 - Stephen Cleary
  • +1使用してしまいましたTask.Runそして元の続き。私の既存のコードがなぜうまくいかないのかについての洞察をありがとうございます。async void。あなたがそれを言ったときもちろんもちろんすべてがもっとずっと理にかなっていました。それではなぜ私は継続を使用しているのですか?さて、ここで説明しようとするのはあまりにも多くのことに関係がありますがこの質問。 - chue x

4

特に非同期タスクを開始するときは、タスクを直接開始するために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
}


  • +1ありがとう、Task.Run私が必要なものです。私が見たことのあるMSDN継続例があるので、私はそれを決して考えませんでしたこのようにつかいますnew Task。彼が説明するので、私はスティーブンの答えを受け入れていますなぜ上記の私のコードがうまくいかない:私の最初のタスクのラムダはasync voidの代わりにasync Task。それではなぜ私は継続を使用しているのですか?さて、ここで説明しようとするのはあまりにも多くのことに関係がありますがこの質問。 - chue x

0

コードをに変更します。

        // 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);


  • 私は通常の使用を避けようとしますWait実行中のスレッドがブロックされ、スレッドがデッドロックする可能性があるためです。見るこの郵便受けデッドロックの詳細については。投稿は使用しますResultの代わりにWaitしかし、両方とも実行中のスレッドをブロックし、両方とも潜在的にデッドロックを引き起こします。 - chue x

リンクされた質問


関連する質問

最近の質問