Task.Yield () ne zaman kullanılır?


218

Async / await ve Taskçok kullanıyorum ama hiç kullanmadım Task.Yield()ve dürüst olmak gerekirse, tüm açıklamalarla bile neden bu yönteme ihtiyaç duyacağımı anlamıyorum.

Birisi Yield()gerekli olduğu yerde iyi bir örnek verebilir mi?

Yanıtlar:


241

Eğer kullandığınız zaman async/ await, sen bunu yaptığında çağrı yöntemi hiçbir garantisi yoktur await FooAsync()aslında uyumsuz çalışacaktır. Dahili uygulama tamamen senkronize bir yol kullanarak geri dönmekte serbesttir.

Engellememenizin kritik olduğu bir yerde API oluşturuyorsanız ve bazı kodları eşzamansız olarak çalıştırıyorsanız ve çağrılan yöntemin senkronize olarak (etkin bir şekilde engelleme) çalışma olasılığı varsa, kullanmak await Task.Yield()yönteminizi eşzamansız olmaya ve geri dönmeye zorlar o noktada kontrol. Kodun geri kalanı geçerli bağlamda daha sonra çalıştırılacaktır (bu noktada yine de eşzamanlı olarak çalışabilir).

Bu, bazı "uzun süre çalışan" başlatma gerektiren asenkron bir yöntem yaparsanız da yararlı olabilir, yani:

 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()Çağrı olmadan, yöntem ilk çağrıya kadar senkronize olarak yürütülür await.


26
Burada bir şeyi yanlış yorumladığımı hissediyorum. Eğer await Task.Yield()kuvvetlerin zaman uyumsuz olması yöntem, neden "gerçek" zaman uyumsuz kod yazarken rahatsız olur? Ağır bir senkronizasyon yöntemi düşünün. Zaman uyumsuz hale getirmek için, eklemek asyncve await Task.Yield()başlangıçta ve sihirli bir şekilde, zaman uyumsuz olacak? Bu hemen hemen tüm senkronizasyon kodunu içine almak Task.Run()ve sahte bir async yöntemi oluşturmak gibi bir şeydir.
Krumelur

14
@Krumelur Büyük bir fark var - örneğime bakın. Bunu Task.Runuygulamak için a kullanırsanız ExecuteFooOnUIThread, UI iş parçacığında değil iş parçacığı havuzunda çalışır. İle await Task.Yield(), sonraki kod hala geçerli bağlamda çalışacak şekilde eşzamansız olmaya zorlarsınız (sadece daha sonraki bir noktada). Normalde yapacağınız bir şey değildir, ancak garip bir nedenden dolayı seçenek olması güzel.
Reed Copsey

7
Bir soru daha: eğer ExecuteFooOnUIThread()çok uzun süre çalışsaydı, hala bir noktada UI iş parçacığını uzun süre engeller ve kullanıcı arayüzünü yanıt vermez, doğru mu?
Krumelur

7
@Krumelur Evet, olur. Hemen değil - daha sonra olur.
Reed Copsey

33
Bu yanıt teknik olarak doğru olsa da, "kodun geri kalan kısmı daha sonra yürütülecektir" ifadesi çok soyuttur ve yanıltıcı olabilir. Task.Yield () sonrasında kodun yürütme programı büyük oranda SynchronisationContext'e bağlıdır. Ve MSDN belgeleri açıkça "Çoğu kullanıcı arabirimi ortamında bir UI iş parçacığında bulunan eşitleme bağlamının genellikle girdi ve oluşturma çalışmalarından daha yüksek bağlama gönderilen işlere öncelik vereceğini belirtir. Bu nedenle, Task.Yield () işlevini beklemeyin ; bir kullanıcı arayüzünü duyarlı tutmak. "
Vitaliy Tsvayer

36

Dahili olarak, await Task.Yield()basitçe ya mevcut senkronizasyon bağlamına veya rastgele bir havuz parçacığı üzerinde devam sıralar, eğer SynchronizationContext.Currentolduğunu null.

Bu edilir verimli uygulanan özel awaiter olarak. Aynı etkiyi üreten daha az verimli bir kod bu kadar basit olabilir:

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()bazı garip yürütme akış değişiklikleri için kısayol olarak kullanılabilir. Örneğin:

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  
}

Yani, w / uygun görev zamanlayıcı Task.Yield()ile değiştirilemez hiçbir durumda düşünemiyorum Task.Factory.StartNew.

Ayrıca bakınız:


Örneğinizde, orada olan ile arasındaki fark nedir var dialogTask = await showAsync();?
Erik Philips

@ErikPhilips, var dialogTask = await showAsync()derleme yapılmaz çünkü await showAsync()ifade bir döndürmez Task(await ). Bununla birlikte, bunu await showAsync()yaptıktan sonra yürütme, yalnızca iletişim kapatıldıktan sonra sürdürülecektir, bu farklıdır. Bunun nedeni window.ShowDialogsenkronize bir API olmasıdır (yine de mesajları pompalamasına rağmen). Bu kodda, iletişim kutusu hala gösterilirken devam etmek istedim.
noseratio

5

Bunun bir kullanımı, Task.Yield()zaman uyumsuz özyineleme yaparken yığın taşmasını önlemektir. Task.Yield()eşzamanlı devam etmeyi önler. Bununla birlikte, bunun bir OutOfMemory istisnasına neden olabileceğini unutmayın (Triynko tarafından belirtildiği gibi). Sonsuz özyineleme hala güvenli değildir ve özyinelemeyi bir döngü olarak yeniden yazmanız daha iyi olur.

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
Bu, bir yığın taşmasını önleyebilir, ancak yeterince uzun süre çalışmasına izin verirseniz, sonunda sistem belleğinin bitmesine neden olur. Her bir yineleme, dış Görev, başka bir iç Görev beklemekte olan bir iç Görev beklediğinden, asla tamamlanmayan yeni bir Görev yaratacaktır. Bu doğru değil. Alternatif olarak, hiçbir zaman tamamlanmayan en dıştaki bir Göreve sahip olabilirsiniz ve sadece recurse yerine döngüye sahip olabilirsiniz. Görev asla tamamlanmayacaktı, ama onlardan sadece biri olacaktı. Döngünün içinde, istediğiniz her şeyi verebilir veya bekleyebilir.
Triynko

Yığın taşmasını yeniden oluşturamıyorum. Bunu await Task.Delay(1)önlemek için yeterli gibi görünüyor . (Konsol Uygulaması, .NET Core 3.1, C # 8)
Theodor Zoulias

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.