回答:
async
/ を使用する場合await
、await 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
ます。
Task.Run
て実装すると、ExecuteFooOnUIThread
UIスレッドではなくスレッドプールで実行されます。を使用await Task.Yield()
すると、後続のコードが現在のコンテキストで実行されるように(後の時点で)、非同期になるように強制できます。これは通常行うことではありませんが、何か奇妙な理由で必要になった場合にオプションがあることは素晴らしいことです。
ExecuteFooOnUIThread()
実行時間が非常に長い場合でも、ある時点で長い間UIスレッドがブロックされ、UIが応答しなくなるでしょう。それは正しいですか。
内部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();
ですか?
var dialogTask = await showAsync()
、await showAsync()
式がを返さないため、コンパイルされませんTask
(なしの場合とは異なりますawait
)。とは言っawait showAsync()
ても、そうした場合、ダイアログが閉じられた後にのみ、その後の実行が再開されます。それが違いです。これwindow.ShowDialog
は、が同期APIであるためです(それでもメッセージが送り出されますが)。そのコードでは、ダイアログが表示されている間は続行したかったのです。
の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();
}
await Task.Delay(1)
を防ぐには十分だと思われます。(コンソールアプリ、.NET Core 3.1、C#8)
Task.Yield()
非同期メソッドのモック実装で使用できます。
await Task.Yield()
力方法は非同期であるためには、なぜ私たちは「本物の」非同期コードを書く気でしょうか?重い同期方法を想像してみてください。それを非同期にするasync
にawait Task.Yield()
は、追加して、最初にそして魔法のように、それは非同期になりますか?これは、すべての同期コードをラップTask.Run()
して偽の非同期メソッドを作成するようなものです。