Bekle vs Görev.Gerekti - Deadlock?


197

Oldukça arasındaki farkı anlayamıyorum Task.Waitve await.

Bir ASP.NET WebAPI hizmetinde aşağıdaki işlevlere benzer bir şey var:

public class TestController : ApiController
{
    public static async Task<string> Foo()
    {
        await Task.Delay(1).ConfigureAwait(false);
        return "";
    }

    public async static Task<string> Bar()
    {
        return await Foo();
    }

    public async static Task<string> Ros()
    {
        return await Bar();
    }

    // GET api/test
    public IEnumerable<string> Get()
    {
        Task.WaitAll(Enumerable.Range(0, 10).Select(x => Ros()).ToArray());

        return new string[] { "value1", "value2" }; // This will never execute
    }
}

Nerede Getçıkacak.

Buna ne sebep olabilir? Bir engelleme beklemesini kullanmak yerine neden bir soruna neden olmaz await Task.Delay?


@Servy: Vaktim olur olmaz bir repo ile geri döneceğim. Şimdilik Task.Delay(1).Wait()yeterince iyi.
ronag

2
Task.Delay(1).Wait()temel olarak aynı şeydir Thread.Sleep(1000). Gerçek üretim kodunda nadiren uygundur.
Servy

@ronag: Sizin WaitAllçıkmaza neden oluyorsunuz . Daha fazla ayrıntı için cevabımdaki bloguma bağlantıya bakın. Bunun await Task.WhenAllyerine kullanmalısınız .
Stephen Cleary

6
@ronag Eğer Çünkü ConfigureAwait(false)bir tek çağrı Barveya Roskilitlenme olmaz, ancak bunların hepsi bekleyen sonra birden fazla yaratmak ve bir enumerator çünkü, ilk çubuk ikinci kilitlenmeye. Eğer varsa await Task.WhenAllyerine ASP bağlamı engellemeyecek şekilde bu, görevlerin tümünü bekleyen, sen yöntem dönüşü normal olarak göreceksiniz.
Servy

2
@ronag Diğer seçenek eklemek olacaktır .ConfigureAwait(false) ağacın yukarı tüm yol sen, bu şekilde hiçbir şey olduğu blok kadar hiç geri ana bağlamına almaya çalışırken; bu işe yarar. Başka bir seçenek, bir iç senkronizasyon bağlamını döndürmek olacaktır. Bağlantı . Eğer içine koyarsanız, herhangi bir yere ihtiyaç duymadan her şeyi etkili Task.WhenAllbir AsyncPump.Runşekilde engeller ConfigureAwait, ancak bu muhtemelen aşırı karmaşık bir çözümdür.
12'de

Yanıtlar:


270

Waitve await- kavramsal olarak benzer olsa da - aslında tamamen farklıdır.

Waitgörev tamamlanıncaya kadar eşzamanlı olarak engellenir. Böylece mevcut iş parçacığı, görevin tamamlanmasını beklerken tam anlamıyla engellenir. Genel bir kural olarak, " asyncsonuna kadar" kullanmalısınız; yani, asynckodu engellemeyin . Blogumda, eşzamansız kodda engellemenin kilitlenmeye nasıl neden olduğuna dair ayrıntılara giriyorum .

awaitgörev tamamlanıncaya kadar eşzamansız olarak bekleyecektir. Bu, geçerli yöntemin "duraklatıldığı" (durumu yakalandığı) ve yöntemin arayan kişiye tamamlanmamış bir görev döndürdüğü anlamına gelir . Daha sonra, awaitifade tamamlandığında, yöntemin geri kalanı bir devamı olarak programlanır.

Ayrıca Wait, üzerinde çalıştığınız bir görevin bekleyen iş parçacığında yürütülebileceğini düşündüğünüz bir "işbirliği bloğundan" bahsettiniz . Bunun olabileceği durumlar var, ancak bu bir optimizasyon. Orada birçok durumlardır edemez görev başka zamanlayıcı içinse gibi olur, ya da çoktan başladı eğer yoksa böyle bir kod örnekte olduğu gibi olmayan bir kod görevi (eğer: Waityürütemediğinden Delayhiçbir kod olmadığından görev inline onun için).

async/ awaitİntro'yu yararlı bulabilirsiniz .


5
Bence bir yanlış anlama var, Waitiyi awaitkilitlenmeler çalışıyor .
ronag

1
Açıkça: kızkardeşimi değiştirirseniz Evet, await Task.Delay(1)birlikte Task.Delay(1).Wait()hizmet aksi takdirde kilitlerinin cezası çalışır.
ronag

5
Hayır, görev zamanlayıcı bunu yapmaz. Waitipliği engeller ve başka şeyler için kullanılamaz.
Stephen Cleary

8
@ronag Benim tahminim sadece yöntem adlarını karışık var ve kilitlenme aslında engelleme kodu neden oldu ve kod ile çalıştı await. Ya bu, ya da çıkmazın hiçbiriyle ilgisi yoktu ve sorunu yanlış teşhis ettiniz.
Servy

3
@hexterminator: Bu tasarım gereğidir - UI uygulamaları için harika çalışır, ancak ASP.NET uygulamaları için engel olma eğilimindedir. ASP.NET Core, SynchronizationContextbir ASP.NET Core isteğinde engelleme artık kilitlenmeleri kaldırarak bu sorunu giderdi.
Stephen Cleary

5

Farklı kaynaklardan okuduğum bilgilere dayanarak:

Bir awaitifade, yürüttüğü iş parçacığını engellemez. Bunun yerine, derleyicinin asyncyöntemin geri kalanını beklenen görevde bir devamı olarak kaydetmesine neden olur . Kontrol daha sonra asyncyöntemin arayanına geri döner . Görev tamamlandığında, devam etmesini başlatır ve asyncyöntemin yürütülmesi kaldığı yerden devam eder.

Tek bir taskkişinin tamamlanmasını beklemek için Task.Waityöntemini çağırabilirsiniz . Bir çağrı Waittek bir sınıf örneği kadar yöntemi blok çağrı iplik yürütme tamamlamıştır. Parametresiz Wait()yöntem, bir görev tamamlanana kadar koşulsuz olarak beklemek için kullanılır. Görev, Thread.Sleepyöntemi iki saniye uyumaya çağırarak işi simüle eder .

Bu makale de iyi bir okuma.


3
"Bu teknik olarak yanlış değil mi? Birisi lütfen açıklığa kavuşturabilir mi?" - açıklığa kavuşturabilir miyim; bunu bir soru olarak mı soruyorsun? (Sadece cevaplamak isteyip istemediğinizi netleştirmek istiyorum). Eğer soruyorsanız: ayrı bir soru olarak daha iyi sonuç verebilir; burada yanıt olarak yeni yanıtlar toplamak pek mümkün değildir
Marc Gravell

1
Soruyu cevapladım ve burada yaşadığım şüphesi için ayrı bir soru sordum stackoverflow.com/questions/53654006/… Teşekkürler @MarcGravell. Şimdi cevap için silme oyunuzu kaldırabilir misiniz?
Ayushmati

"Lütfen cevap için silme oyunuzu şimdi kaldırabilir misiniz?" - bu benim değil; ♦ sayesinde, böyle bir oylama hemen yürürlüğe girecekti. Bununla birlikte, sorunun kilitlenme davranışı ile ilgili sorunun kilit noktalarına cevap verdiğini düşünmüyorum.
Marc Gravell

Bu doğru değil. İlk gelene kadar her şey engellenmez
user1785960

-2

Diğer cevaplarda bazı önemli olgular verilmedi:

"zaman uyumsuz beklemek" CIL düzeyinde daha karmaşıktır ve bu nedenle bellek ve CPU zamanına mal olur.

Bekleme süresi kabul edilemez olduğunda herhangi bir görev iptal edilebilir.

"Async bekliyor" durumunda, böyle bir görevin iptal edilmesi veya izlenmesi için bir işleyicimiz yoktur.

Görev'i kullanmak "async bekliyor" dan daha esnektir.

Herhangi bir senkronizasyon işlevi zaman uyumsuz olarak sarılabilir.

public async Task<ActionResult> DoAsync(long id) 
{ 
    return await Task.Run(() => { return DoSync(id); } ); 
} 

"async bekliyor" birçok sorun üretir. Biz şimdi beklemiyoruz ifadesi çalışma zamanı ve bağlam hata ayıklama olmadan ulaşılacaktır. İlk önce ulaşılamazsa her şey engellenir . Bazen bile beklendiği gibi ulaşılıyor gibi görünüyor hala her şey engellendi:

https://github.com/dotnet/runtime/issues/36063

Neden senkronizasyon ve zaman uyumsuzluk yöntemi veya kesmek kullanarak kod çoğaltma ile yaşamak zorunda görmüyorum.

Sonuç: Görevi elle oluşturun ve kontrol etmek çok daha iyidir. Görev işleyici daha fazla kontrol sağlar. Görevleri izleyebilir ve yönetebiliriz:

https://github.com/lsmolinski/MonitoredQueueBackgroundWorkItem

İngilizcem için üzgünüm.

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.