いつTask.Yield()を使用しますか?


218

私はasync / awaitなどTaskを使用Task.Yield()していますが、これまで使用したことがなく、すべての説明があっても正直なところ、この方法が必要な理由がわかりません。

誰かYield()が必要な場所で良い例を示すことができますか?

回答:


241

async/ を使用する場合awaitawait FooAsync()実行時に呼び出すメソッドが実際に非同期で実行される保証はありません。内部実装は、完全同期パスを使用して自由に戻ります。

ブロックしないことが重要なAPIを作成していて、コードを非同期で実行し、呼び出されたメソッドが同期して(事実上ブロックされて)実行される可能性がある場合、を使用await Task.Yield()すると、メソッドが強制的に非同期になり、その時点で制御します。コードの残りの部分は、現在のコンテキストで後で実行されます(その時点で、まだ同期的に実行されている可能性があります)。

これは、「長時間実行」初期化を必要とする非同期メソッドを作成する場合にも役立ちます。

 private async void button_Click(object sender, EventArgs e)
 {
      await Task.Yield(); // Make us async right away

      var data = ExecuteFooOnUIThread(); // This will run on the UI thread at some point later

      await UseDataAsync(data);
 }

Task.Yield()呼び出しがない場合、メソッドは最初の呼び出しまで同期して実行されawaitます。


26
ここで何かを誤解しているような気がします。場合await Task.Yield()力方法は非同期であるためには、なぜ私たちは「本物の」非同期コードを書く気でしょうか?重い同期方法を想像してみてください。それを非同期にするasyncawait Task.Yield()は、追加して、最初にそして魔法のように、それは非同期になりますか?これは、すべての同期コードをラップTask.Run()して偽の非同期メソッドを作成するようなものです。
Krumelur 2014年

14
@Krumelur大きな違いがあります-私の例を見てください。を使用しTask.Runて実装すると、ExecuteFooOnUIThreadUIスレッドではなくスレッドプールで実行されます。を使用await Task.Yield()すると、後続のコードが現在のコンテキストで実行されるように(後の時点で)、非同期になるように強制できます。これは通常行うことではありませんが、何か奇妙な理由で必要になった場合にオプションがあることは素晴らしいことです。
リードコプシー2014年

7
もう1つの質問:ExecuteFooOnUIThread()実行時間が非常に長い場合でも、ある時点で長い間UIスレッドがブロックされ、UIが応答しなくなるでしょう。それは正しいですか。
Krumelur 2014年

7
@Krumelurはい、そうです。ただすぐにではなく、後で起こります。
リードコプシー、2014年

33
この答えは技術的には正しいですが、「残りのコードは後で実行される」という記述は抽象的すぎて誤解を招く可能性があります。Task.Yield()の後のコードの実行スケジュールは、具体的なSynchronizationContextに大きく依存します。また、MSDNのドキュメントには、「ほとんどのUI環境のUIスレッドに存在する同期コンテキストは、多くの場合、入力およびレンダリング作業よりも高いコンテキストに投稿された作業を優先します。このため、待機するTask.Yield()に依存しないでください。 ; UIの応答性を維持するため。」
Vitaliy Tsvayer 2015

36

内部await Task.Yield()的には、現在の同期コンテキスト、またはの場合SynchronizationContext.Currentはランダムプールスレッドで継続をキューに入れるだけですnull

それはされて効率的に実装されたカスタムawaiterとして。同じ効果を生み出す非効率的なコードは、次のように単純かもしれません。

var tcs = new TaskCompletionSource<bool>();
var sc = SynchronizationContext.Current;
if (sc != null)
    sc.Post(_ => tcs.SetResult(true), null);
else
    ThreadPool.QueueUserWorkItem(_ => tcs.SetResult(true));
await tcs.Task;

Task.Yield()いくつかの奇妙な実行フローの変更のショートカットとして使用できます。例えば:

async Task DoDialogAsync()
{
    var dialog = new Form();

    Func<Task> showAsync = async () => 
    {
        await Task.Yield();
        dialog.ShowDialog();
    }

    var dialogTask = showAsync();
    await Task.Yield();

    // now we're on the dialog's nested message loop started by dialog.ShowDialog 
    MessageBox.Show("The dialog is visible, click OK to close");
    dialog.Close();

    await dialogTask;
    // we're back to the main message loop  
}

とはいえ、適切なタスクスケジューラTask.Yield()で置き換えられないケースは考えられませんTask.Factory.StartNew

以下も参照してください。


あなたの例では、そこにあるものと何が違うのvar dialogTask = await showAsync();ですか?
Erik Philips、

@ErikPhilipsはvar dialogTask = await showAsync()await showAsync()式がを返さないため、コンパイルされませんTask(なしの場合とは異なりますawait)。とは言っawait showAsync()ても、そうした場合、ダイアログが閉じられた後にのみ、その後の実行が再開されます。それが違いです。これwindow.ShowDialogは、が同期APIであるためです(それでもメッセージが送り出されますが)。そのコードでは、ダイアログが表示されている間は続行したかったのです。
noseratio

5

の1つの用途は、Task.Yield()非同期再帰を実行するときにスタックオーバーフローを防ぐことです。Task.Yield()同期継続を防ぎます。ただし、これによりOutOfMemory例外が発生する可能性があることに注意してください(Triynkoが指摘)。無限の再帰はまだ安全ではなく、おそらく再帰をループとして書き直す方がよいでしょう。

private static void Main()
    {
        RecursiveMethod().Wait();
    }

    private static async Task RecursiveMethod()
    {
        await Task.Delay(1);
        //await Task.Yield(); // Uncomment this line to prevent stackoverlfow.
        await RecursiveMethod();
    }

4
これにより、スタックオーバーフローを防ぐことができますが、長時間実行すると、最終的にシステムメモリが不足します。外側のタスクが内側のタスクを待っており、内側のタスクがさらに別の内側のタスクを待っているなどのように、各反復は決して完了しない新しいタスクを作成します。これは問題です。または、単に完了しない最外部のタスクを1つ作成し、再帰する代わりにループさせるだけでもかまいません。タスクは決して完了しませんが、そのうちの1つだけが存在します。ループの中で、それはあなたが好きなものを譲るか、待つことができます。
Triynko

スタックオーバーフローを再現できません。それawait Task.Delay(1)を防ぐには十分だと思われます。(コンソールアプリ、.NET Core 3.1、C#8)
Theodor Zoulias

-8

Task.Yield() 非同期メソッドのモック実装で使用できます。


4
詳細を入力してください。
PJProudhon 2018年

3
この目的のために、私はむしろTask.CompletedTaskを使用します -詳細については、このmsdnブログ投稿のTask.CompletedTaskセクションを参照してください。
Grzegorz Smulko 2018年

2
Task.CompletedTask、またはTask.FromResultを使用する際の問題は、メソッドが非同期で実行された場合にのみ発生するバグを見逃してしまう可能性があることです。
Joakim MH
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.