Jawaban:
Ketika Anda menggunakan async
/ await
, tidak ada jaminan bahwa metode yang Anda panggil ketika Anda await FooAsync()
benar-benar akan berjalan secara tidak sinkron. Implementasi internal bebas untuk kembali menggunakan jalur yang sepenuhnya sinkron.
Jika Anda membuat API yang penting agar Anda tidak memblokir dan Anda menjalankan beberapa kode secara tidak sinkron, dan ada kemungkinan bahwa metode yang dipanggil akan berjalan secara sinkron (secara efektif memblokir), menggunakan await Task.Yield()
akan memaksa metode Anda menjadi tidak sinkron, dan kembali kontrol pada saat itu. Sisa kode akan dieksekusi di lain waktu (pada titik itu, masih dapat berjalan secara sinkron) pada konteks saat ini.
Ini juga dapat berguna jika Anda membuat metode asinkron yang memerlukan inisialisasi "lama berjalan", yaitu:
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);
}
Tanpa Task.Yield()
panggilan, metode ini akan mengeksekusi secara sinkron hingga panggilan pertama await
.
Task.Run
untuk mengimplementasikannya, ExecuteFooOnUIThread
akan berjalan di kumpulan utas, bukan utas UI. Dengan await Task.Yield()
, Anda memaksanya untuk tidak sinkron dengan cara kode berikutnya masih berjalan pada konteks saat ini (hanya pada titik waktu kemudian). Ini bukan sesuatu yang biasa Anda lakukan, tetapi itu menyenangkan bahwa ada pilihan jika itu diperlukan untuk beberapa alasan aneh.
ExecuteFooOnUIThread()
berjalan sangat lama, masih akan memblokir utas UI untuk waktu yang lama di beberapa titik dan membuat UI tidak responsif, apakah itu benar?
Secara internal, await Task.Yield()
cukup antri kelanjutan pada konteks sinkronisasi saat ini atau pada utas kumpulan acak, jika SynchronizationContext.Current
ada null
.
Ini secara efisien diimplementasikan sebagai penunggu kustom. Kode yang kurang efisien menghasilkan efek yang identik mungkin sesederhana ini:
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()
dapat digunakan sebagai jalan pintas untuk beberapa perubahan alur eksekusi yang aneh. Sebagai contoh:
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
}
Yang mengatakan, saya tidak bisa memikirkan kasus apa pun di mana Task.Yield()
tidak dapat diganti dengan Task.Factory.StartNew
tugas penjadwal yang tepat
Lihat juga:
var dialogTask = await showAsync();
?
var dialogTask = await showAsync()
tidak akan dikompilasi karena await showAsync()
ekspresi tidak mengembalikan a Task
(tidak seperti itu tanpa await
). Yang mengatakan, jika Anda melakukannya await showAsync()
, eksekusi setelah itu akan dilanjutkan hanya setelah dialog telah ditutup, itulah bedanya. Itu karena window.ShowDialog
API yang disinkronkan (meskipun masih memompa pesan). Dalam kode itu, saya ingin melanjutkan sementara dialog masih ditampilkan.
Salah satu penggunaannya Task.Yield()
adalah untuk mencegah stack overflow ketika melakukan rekursi async. Task.Yield()
mencegah kelanjutan sinkron. Namun, perlu diketahui bahwa ini dapat menyebabkan pengecualian OutOfMemory (seperti dicatat oleh Triynko). Rekursi tak berujung masih tidak aman dan Anda mungkin lebih baik menulis ulang rekursi sebagai sebuah loop.
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)
sudah cukup untuk mencegahnya. (Aplikasi Konsol, .NET Core 3.1, C # 8)
Task.Yield()
dapat digunakan dalam implementasi tiruan dari metode async.
await Task.Yield()
memaksa metode menjadi async, mengapa kita repot-repot menulis kode async "asli"? Bayangkan metode sinkronisasi yang berat. Untuk membuatnya async, cukup tambahkanasync
danawait Task.Yield()
pada awalnya dan secara ajaib, itu akan async? Itu akan seperti membungkus semua kode sinkronisasi ke dalamTask.Run()
dan membuat metode async palsu.