Tüm sunucu tarafı kodu için ConfigureAwait'i çağırmak için en iyi yöntem


561

Sunucu tarafı kodunuz varsa (yani bazıları ApiController) ve işlevleriniz eşzamansız olduğunda - böylece geri döner Task<SomeObject>- çağırdığınız işlevleri beklediğiniz her zaman en iyi yöntem olarak kabul edilir ConfigureAwait(false)mi?

İplik bağlamlarını orijinal iplik bağlamına geri döndürmek zorunda olmadığından daha performanslı olduğunu okumuştum. Ancak, ASP.NET Web Api ile, isteğiniz bir iş parçacığında geliyorsa ve işlevinizin ConfigureAwait(false)son sonucunu döndürürken sizi farklı bir iş parçacığına sokabilecek bazı işlev ve çağrıları bekliyorsanız ApiController.

Aşağıda bahsettiğim şeyin bir örneğini yazdım:

public class CustomerController : ApiController
{
    public async Task<Customer> Get(int id)
    {
        // you are on a particular thread here
        var customer = await SomeAsyncFunctionThatGetsCustomer(id).ConfigureAwait(false);

        // now you are on a different thread!  will that cause problems?
        return customer;
    }
}

Yanıtlar:


628

Güncelleştirme: ASP.NET Core'da birSynchronizationContext . ASP.NET Core kullanıyorsanız, kullanıp kullanmamanız önemli ConfigureAwait(false)değildir.

ASP.NET "Tam" veya "Klasik" veya her neyse, bu yanıtın geri kalanı hala geçerlidir.

Orijinal gönderi (Core olmayan ASP.NET için):

ASP.NET ekibinin bu videosu, ASP.NET'te kullanma hakkında en iyi bilgiye sahiptir async.

İplik bağlamlarını orijinal iplik bağlamına geri döndürmek zorunda olmadığından daha performanslı olduğunu okumuştum.

Bu, "senkronize etmeniz" gereken tek bir UI iş parçacığının bulunduğu UI uygulamaları için geçerlidir.

ASP.NET'te durum biraz daha karmaşıktır. Bir asyncyöntem yürütmeyi sürdürdüğünde, ASP.NET iş parçacığı havuzundan bir iş parçacığı alır. Bağlam yakalamayı kullanarak devre dışı bırakırsanız ConfigureAwait(false), iş parçacığı yalnızca yöntemi doğrudan yürütmeye devam eder. Bağlam yakalamayı devre dışı bırakmazsanız, iş parçacığı istek bağlamını yeniden girer ve sonra yöntemi yürütmeye devam eder.

Yani ConfigureAwait(false)size ASP.NET bir iş parçacığı atlama kaydetmez; istek içeriğinin yeniden girilmesini önler, ancak bu normalde çok hızlıdır. ConfigureAwait(false) olabilir Eğer bir istek paralel işlem küçük bir miktar yapmaya çalışıyoruz, ama gerçekten TPL bu senaryoların çoğu için daha uygun olup olmadığını yararlı olabilir.

Ancak, ASP.NET Web Api ile, isteğiniz bir iş parçacığında geliyorsa ve ApiController işlevinizin son sonucunu döndürürken potansiyel olarak farklı bir iş parçacığına neden olabilecek ConfigureAwait (false) işlevini çağırırsanız .

Aslında, sadece bir awaitkutu yapmak bunu yapabilir. Senin kez asyncyöntem vurur await, yöntem ama engellenir parçacığı iplik havuzuna geri döner. Yöntem devam etmeye hazır olduğunda, herhangi bir evre iş parçacığı havuzundan kapar ve yöntemi sürdürmek için kullanılır.

ConfigureAwaitASP.NET'te yapılan tek fark , iş parçacığının yöntemi devam ettirirken istek bağlamına girip girmeyeceğidir.

MSDN makalemdeSynchronizationContext ve asyncgiriş blog yayınımda daha fazla arka plan bilgisi var .


23
Yerel iş parçacığı depolama alanı herhangi bir bağlamdan akmaz . HttpContext.Current, SynchronizationContextvarsayılan olarak siz tarafından akan ASP.NET tarafından akar await, ancak akmaz ContinueWith. Otoh, (güvenlik kısıtlamaları dahil) Yürütme içeriği C # yoluyla CLR belirtilen bağlam olduğunu ve olan her ikisi tarafından aktı ContinueWithve await(eğer kullansanız bile ConfigureAwait(false)).
Stephen Cleary

65
C # 'ın ConfigureAwait (false) için yerel dil desteği olsaydı harika olmaz mıydı? 'Bekliyor' gibi bir şey (bağlamı beklemez). Her yerde ayrı bir yöntem çağrısı yazmak oldukça sinir bozucu. :)
NathanAldenSr

19
@NathanAldenSr: Biraz tartışıldı. Yeni bir anahtar kelimeyle ilgili sorun, ConfigureAwaitaslında yalnızca görevleri beklerken mantıklı olmasıdır , oysa awaitherhangi bir "beklenebilir" ifadeyle hareket eder. Dikkate alınan diğer seçenekler şunlardır: Varsayılan davranış bir kütüphanedeyse bağlamı atmalı mıdır? Veya varsayılan bağlam davranışı için bir derleyici ayarınız var mı? Her ikisi de reddedildi çünkü kodu okumak ve ne yaptığını söylemek daha zor.
Stephen Cleary

10
@AnshulNigam: Bu yüzden denetleyici eylemlerinin bağlamlarına ihtiyacı vardır. Ancak eylemlerin çağırdığı çoğu yöntem bunu yapmaz.
Stephen Cleary

14
@JonathanRoeder: Genel olarak konuşursak, / tabanlı bir kilitlenmeden ConfigureAwait(false)kaçınmanız gerekmez , çünkü ASP.NET'te ilk olarak / kullanmamalısınız . ResultWaitResultWait
Stephen Cleary

131

Sorunuza kısa bir cevap: Hayır ConfigureAwait(false). Uygulama düzeyinde bu şekilde arama yapmamalısınız .

TL; Uzun cevabın DR versiyonu: Tüketicinizi tanımadığınız ve senkronizasyon bağlamına (inandığım bir kütüphanede olmamalı) ihtiyaç duymadığınız bir kütüphane yazıyorsanız, daima kullanmalısınız ConfigureAwait(false). Aksi takdirde, kitaplığınızdaki tüketiciler eşzamansız yöntemlerinizi engelleyici bir şekilde tüketerek çıkmaza girebilir. Bu duruma bağlıdır.

İşte ConfigureAwaityöntemin önemi hakkında biraz daha ayrıntılı açıklama (blog yazımdan bir alıntı):

Await anahtar sözcüğünü içeren bir yöntem beklerken, derleyici sizin adınıza bir grup kod oluşturur. Bu eylemin amaçlarından biri, UI (veya ana) iş parçacığı ile eşitlemeyi işlemektir. Bu özelliğin temel bileşeni SynchronizationContext.Current, geçerli iş parçacığı için eşitleme bağlamını alan bileşendir . SynchronizationContext.Currentbulunduğunuz ortama bağlı olarak doldurulur. GetAwaiterGörev yöntemi aranır SynchronizationContext.Current. Geçerli senkronizasyon bağlamı boş değilse, bu bekleyiciye aktarılan devam, bu senkronizasyon bağlamına geri gönderilir.

Yeni eşzamansız dil özelliklerini kullanan bir yöntemi engelleme biçiminde tüketirken, kullanılabilir bir SynchronizationContext'iniz varsa bir kilitlenme ile karşılaşırsınız. Bu yöntemleri engelleme tarzında (Bekleme ile Görev yöntemini beklerken veya sonucu doğrudan Görevin Sonuç özelliğinden alırken) tüketirken, ana iş parçacığını aynı anda engellersiniz. Sonunda, Görev threadpool'daki bu yöntem içinde tamamlandığında SynchronizationContext.Current, kullanılabilir ve yakalandığı için devam etmeyi ana iş parçacığına geri göndermeye çağırır . Ancak burada bir sorun var: UI iş parçacığı engellendi ve bir kilitlenme var!

Ayrıca, tam olarak sorunuz için olan iki harika makale:

Son olarak, Lucian Wischik'ten tam olarak bu konuda kısa bir video var : Async kütüphane yöntemleri Task.ConfigureAwait (false) kullanmayı düşünmelidir .

Bu yardımcı olur umarım.


2
"Görev GetAwaiter yöntemi SynchronizationContext.Current arar. Geçerli senkronizasyon bağlamı boş değilse, bu bekleyiciye iletilen devam, bu senkronizasyon bağlamına geri gönderilir." - TaskYığını almak için yürüyüşe çıktığını söylemeye çalıştığın izlenimini alıyorum SynchronizationContext, bu yanlış. SynchronizationContextÇağrısı önce yakaladı olduğu Tasküzerinde devam edilir ve daha sonra kod kalanı SynchronizationContextise SynchronizationContext.Currentboş değil.
casperOne

1
@casperOne Aynı şeyi söylemeyi amaçladım.
tugberk

8
Sınıf kütüphanesinin her yerine yazmak yerine SynchronizationContext.Currentaçık / / veya kütüphanenin çağrıldığından emin olmak arayanın sorumluluğunda değil midir? Task.Run().ConfigureAwait(false)
binki

1
@binki - Öte yandan: (1) muhtemelen bir kütüphane birçok uygulamada kullanılır, bu yüzden kütüphanelerde uygulamalarda bir kez daha kolaylık sağlamak için çaba harcamak maliyet etkindir; (2) muhtemelen kütüphane yazarı, bu bağlamda ifade ettiği orijinal bağlam üzerinde devam etmesini gerektirecek hiçbir nedeni olmayan bir kod yazdığını bilir .ConfigureAwait(false). Belki de bu varsayılan davranış olsaydı kütüphane yazarları için daha kolay olurdu, ama bir kütüphane doğru yazmayı biraz zorlaştırmanın, bir uygulamayı doğru yazmayı biraz zorlaştırmaktan daha iyi olduğunu düşünürdüm.
ToolmakerSteve

4
Bir kütüphanenin yazarı neden tüketiciyi kodlamalıdır? Tüketici kilitlenmeyi istiyorsa, neden onları engellemeliyim?
Quarkly

25

ConfigureAwait (false) kullanarak bulduğum en büyük dezavantaj, iş parçacığı kültürünün sistem varsayılanına döndürülmesidir. Örneğin bir kültür yapılandırdıysanız ...

<system.web>
    <globalization culture="en-AU" uiCulture="en-AU" />    
    ...

ve kültürü en-US olarak ayarlanmış bir sunucuda barındırıyorsanız o zaman ConfigureAwait (false) CultureInfo olarak adlandırılmadan önce bulacaksınız. CurrentCulture en-AU'ya dönecek ve en-US alacaksınız. yani

// CultureInfo.CurrentCulture ~ {en-AU}
await xxxx.ConfigureAwait(false);
// CultureInfo.CurrentCulture ~ {en-US}

Uygulamanız kültüre özgü verilerin biçimlendirilmesini gerektiren herhangi bir şey yapıyorsa, ConfigureAwait (false) kullanırken bunu dikkate almanız gerekir.


27
.NET (Ben 4,6 beri düşünüyorum?) Modern sürümleri bile parçacıkları arasında kültürünü yaymak olacaktır ConfigureAwait(false)kullanılır.
Stephen Cleary

1
Bilgi için teşekkürler. Gerçekten .net 4.5.2
Mick

11

Aşağıdakilerin uygulanması hakkında bazı genel düşüncelerim var Task:

  1. Görev tek kullanımlıktır ancak kullanmamız gerekmemektedirusing .
  2. ConfigureAwait4.5 yılında tanıtıldı. Task4.0 yılında tanıtıldı.
  3. .NET Threads her zaman içeriği akıtmak için kullanılır (CLR kitabı üzerinden C # 'a bakın) ancak varsayılan uygulamalarında Task.ContinueWithb / c yapmazlar, bağlam anahtarının pahalı olduğu ve varsayılan olarak kapalı olduğu fark edildi.
  4. Sorun, bir kütüphane geliştiricisinin istemcilerinin bağlam akışına ihtiyaç duyup duymadığı umurumda olmamalıdır, dolayısıyla bağlamın akıp akmadığına karar vermemelidir.
  5. [Daha sonra eklendi] Yetkili bir cevap ve uygun referans olmaması ve bununla mücadele etmeye devam etmemiz, birisinin işini doğru yapmadığı anlamına gelir.

Birkaç var Mesajları konuda ama benim almak - Tuğberk en güzel cevap ek olarak - yani tüm API'ler asenkron açmak ve ideal bağlamı akmalıdır. Zaman uyumsuzluk yaptığınız için, beklemek yerine süreklilikleri kullanabilirsiniz, böylece kütüphanede hiçbir bekleme yapılmadığından ve bağlam korunacak şekilde (HttpContext gibi) akışı sürdürdüğünüz için kilitlenmenin nedeni olmayacaktır.

Sorun, bir kitaplığın eşzamanlı API'yı göstermesi ancak başka bir eşzamansız API kullanmasıdır; bu nedenle kodunuzda Wait()/ kullanmanız gerekir Result.


6
1) İsterseniz arayabilirsiniz Task.Dispose; zamanın büyük çoğunluğuna ihtiyacınız yok. 2) TaskTPL'nin bir parçası olarak .NET 4.0'da kullanılmaya başlandı ConfigureAwait; eklendiğinde async, Taskyeni bir tür icat etmek yerine mevcut türü yeniden kullandılar Future.
Stephen Cleary

6
3) İki farklı "bağlam" türünü karıştırıyorsunuz. CLR yoluyla C # 'da bahsedilen "bağlam" her zaman akar Task; tarafından kontrol edilen "bağlam" ContinueWitha SynchronizationContextveya şeklindedir TaskScheduler. Bu farklı bağlamlar Stephen Toub'un blogunda ayrıntılı olarak açıklanmıştır .
Stephen Cleary

21
4) Kütüphane yazarının, her asenkron yöntem bağımsız olarak devam ettiği için, arayanlarının içerik akışına ihtiyacı olup olmadığı konusunda dikkatli olması gerekmez. Bu nedenle, arayanlar içerik akışına ihtiyaç duyarlarsa, kitaplık yazarının akışı sağlayıp sağlamadığına bakılmaksızın akışı akabilirler.
Stephen Cleary

1
İlk başta, soruyu cevaplamak yerine şikayet ediyor gibi görünüyorsunuz. Ve sonra “bağlam” dan bahsediyorsunuz, ancak .Net'te çeşitli bağlam türleri var ve hangisinden (veya hangilerinden?) Bahsediyorsunuz. Ve kendinizi karıştırmasanız bile (ama siz öyle olduğunu düşünüyorum, eskiden Threads ile akan bir bağlam olmadığına inanıyorum , ama artık değil ContinueWith()), bu cevabınızı okumak için kafa karıştırıcı hale getiriyor.
svick

1
@StephenCleary evet, lib dev'in bilmesine gerek yok, istemciye bağlı. Bunu açıkça ifade ettiğimi sanıyordum, ama öbeklerim net değildi.
Aliostad
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.