Kapan saya akan menggunakan Task.Yield ()?

218

Saya menggunakan async / menunggu dan Taskbanyak tetapi tidak pernah menggunakan Task.Yield()dan jujur ​​bahkan dengan semua penjelasan saya tidak mengerti mengapa saya membutuhkan metode ini.

Adakah yang bisa memberikan contoh yang baik di mana Yield()diperlukan?

Krumelur
sumber

Jawaban:

241

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.

Reed Copsey
sumber
26
Saya merasa seperti saya salah menafsirkan sesuatu di sini. Jika await Task.Yield()memaksa metode menjadi async, mengapa kita repot-repot menulis kode async "asli"? Bayangkan metode sinkronisasi yang berat. Untuk membuatnya async, cukup tambahkan asyncdan await Task.Yield()pada awalnya dan secara ajaib, itu akan async? Itu akan seperti membungkus semua kode sinkronisasi ke dalam Task.Run()dan membuat metode async palsu.
Krumelur
14
@ Karaelur Ada perbedaan besar - lihat contoh saya. Jika Anda menggunakan a Task.Rununtuk mengimplementasikannya, ExecuteFooOnUIThreadakan 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.
Reed Copsey
7
Satu pertanyaan lagi: jika ExecuteFooOnUIThread()berjalan sangat lama, masih akan memblokir utas UI untuk waktu yang lama di beberapa titik dan membuat UI tidak responsif, apakah itu benar?
Krumelur
7
@ Karaelur Ya, tentu saja. Hanya tidak segera - itu akan terjadi di lain waktu.
Reed Copsey
33
Meskipun jawaban ini secara teknis benar, pernyataan bahwa "sisa kode akan dieksekusi di lain waktu" terlalu abstrak dan mungkin menyesatkan. Jadwal eksekusi kode setelah Task.Yield () sangat bergantung pada Context Synchronisation. Dan dokumentasi MSDN dengan jelas menyatakan bahwa "Konteks sinkronisasi yang ada pada utas UI di sebagian besar lingkungan UI akan sering memprioritaskan pekerjaan yang diposting pada konteks lebih tinggi daripada pekerjaan input dan rendering. Untuk alasan ini, jangan mengandalkan menunggu Task.Yield () ; untuk menjaga UI responsif. "
Vitaliy Tsvayer
36

Secara internal, await Task.Yield()cukup antri kelanjutan pada konteks sinkronisasi saat ini atau pada utas kumpulan acak, jika SynchronizationContext.Currentada 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.StartNewtugas penjadwal yang tepat

Lihat juga:

noseratio
sumber
Dalam contoh Anda, apa perbedaan antara apa yang ada di sana dan var dialogTask = await showAsync();?
Erik Philips
@ErikPhilips, 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.ShowDialogAPI yang disinkronkan (meskipun masih memompa pesan). Dalam kode itu, saya ingin melanjutkan sementara dialog masih ditampilkan.
noseratio
5

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();
    }
Joakim MH
sumber
4
Ini mungkin mencegah stack overflow, tetapi ini pada akhirnya akan kehabisan memori sistem jika Anda membiarkannya berjalan cukup lama. Setiap iterasi akan membuat Tugas baru yang tidak pernah selesai, karena Tugas luar sedang menunggu Tugas batin, yang sedang menunggu Tugas batin lainnya, dan seterusnya. Ini tidak baik. Atau, Anda bisa saja memiliki satu Tugas terluar yang tidak pernah selesai, dan hanya memiliki loop itu daripada berulang. Tugas tidak akan pernah selesai, tetapi hanya akan ada satu dari mereka. Di dalam lingkaran, itu bisa menghasilkan atau menunggu apa pun yang Anda suka.
Triynko
Saya tidak dapat mereproduksi stack overflow. Tampaknya itu await Task.Delay(1)sudah cukup untuk mencegahnya. (Aplikasi Konsol, .NET Core 3.1, C # 8)
Theodor Zoulias
-8

Task.Yield() dapat digunakan dalam implementasi tiruan dari metode async.

mhsirig
sumber
4
Anda harus memberikan beberapa detail.
PJProudhon
3
Untuk tujuan ini, saya lebih suka menggunakan Task.CompletedTask - lihat bagian Task.CompletedTask di posting blog msdn ini untuk pertimbangan lebih lanjut.
Grzegorz Smulko
2
Masalah dengan menggunakan Task.CompletedTask, atau Task.FromResult adalah bahwa Anda bisa kehilangan bug yang hanya muncul ketika metode dijalankan secara tidak sinkron.
Joakim MH