Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
Bir kütüphane yalnızca asenkron API'leri kullanıma sunarken, tüketiciler bazen zaman uyumlu bir arabirime veya sözleşmeye uymak için bunları zaman uyumlu çağrılarda sarmalar. Bu "sync-over-async" deseni basit görünebilir, ancak kilitlenmelere ve performans sorunlarına sıkça neden olur.
Temel sarmalama desenleri
Görev Tabanlı Zaman Uyumsuz Desen (TAP) yönteminin etrafındaki zaman uyumlu sarmalayıcı, görevin Result özelliğine erişir ve bu da çağıran iş parçacığını engeller:
public class TapWrapper
{
public static int Foo(Func<Task<int>> fooAsync)
{
return fooAsync().Result;
}
}
Public Module TapWrapper
Public Function Foo(fooAsync As Func(Of Task(Of Integer))) As Integer
Return fooAsync().Result
End Function
End Module
Bu yaklaşım basit görünür, ancak çalıştığı ortama bağlı olarak ciddi sorunlara neden olabilir.
Tek iş parçacıklı bağlamlarda kilitlenmeler
En tehlikeli senaryo, tek iş parçacıklı bir SynchronizationContext iş parçacığından zaman uyumlu bir sarmalayıcı çağırdığınızda oluşur. Bu senaryo genellikle WPF, Windows Forms veya MAUI uygulamalarında bir kullanıcı arabirimi iş parçacığıdır.
public static class DeadlockExample
{
private static void Delay(int milliseconds)
{
DelayAsync(milliseconds).Wait();
}
private static async Task DelayAsync(int milliseconds)
{
await Task.Delay(milliseconds);
}
}
Public Module DeadlockExample
Private Sub Delay(milliseconds As Integer)
DelayAsync(milliseconds).Wait()
End Sub
Private Async Function DelayAsync(milliseconds As Integer) As Task
Await Task.Delay(milliseconds)
End Function
End Module
Adım adım şöyle olur:
- Kullanıcı arabirimi iş parçacığı
Delay'ı çağırır ve bu daDelayAsync(milliseconds).Wait()'i çağırır. -
DelayAsync,await Task.Delay(milliseconds)öğesine ulaşana kadar eşzamanlı olarak çalışır. - Gecikme henüz tamamlanmadığından geçerli
awaitSynchronizationContext değeri yakalar ve askıya alır.DelayAsyncçağırana bir Task döndürür. - kullanıcı arabirimi iş parçacığı,
.Wait()içinde bu görevin tamamlanmasını bekler. - Gecikme tamamlandığında, devamın kullanıcı arabirimi iş parçacığı olan özgün
SynchronizationContextüzerinde çalıştırılması gerekir. - Kullanıcı arabirimi iş parçacığı
.Wait()nedeniyle bloklandığı için devam işlemini gerçekleştiremiyor. - Kilitlenme.
Önemli
Eşzamansız üzerinde eşzamanlı kodun başarısı veya başarısızlığı, çalıştığı ortama bağlıdır. Konsol uygulamasında çalışan kod, kullanıcı arabirimi iş parçacığında veya ASP.NET 'de (.NET Framework'te) kilitlenmeye neden olabilir. Bu ortam bağımlılığı, zaman uyumlu sarmalayıcıları ortaya çıkarmaktan kaçınmanın temel bir nedenidir.
İş parçacığı havuzu tükenmesi
Kilitlenmeler kullanıcı arabirimi iş parçacıklarıyla sınırlı değildir. Eşzamanlı olmayan bir yöntem, çalışmasını tamamlamak için iş parçacığı havuzuna dayanıyorsa, örneğin son işleme adımını sıraya koyarak, senkron sarıcılar ile havuzdaki birçok iş parçacığını engellemek havuzu aç bırakabilir.
public static class ThreadPoolDeadlockExample
{
public static int Foo(Func<Task<int>> fooAsync)
{
return fooAsync().Result;
}
public static async Task DemonstrateDeadlockRiskAsync()
{
var tasks = Enumerable.Range(0, 25)
.Select(_ => Task.Run(() => Foo(() => SomeIOOperationAsync())));
await Task.WhenAll(tasks);
}
private static async Task<int> SomeIOOperationAsync()
{
await Task.Delay(100);
return 42;
}
}
Bu senaryoda:
- Birçok iş parçacığı havuzu iş parçacıkları
Foo'yi çağırarak.Resultiçinde bloke eder. - Her bir uyumsuz işlem G/Ç'sini tamamlar ve tamamlama geri çağırmasını çalıştırmak için bir iş parçacığı havuzundaki bir iş parçacığına ihtiyaç duyar.
- Engellenen çağrılar kullanılabilir çalışan iş parçacıklarını kapladığı için, bir iş parçacığının kullanılabilir duruma gelmesi için tamamlanmaların uzun süre beklemesi gerekebilir.
- Modern .NET zaman içinde iş parçacığı havuzuna daha fazla iş parçacığı ekleyebilir, ancak uygulama yine de ciddi iş parçacığı havuzu yetersizliği, düşük aktarım hızı, uzun gecikmeler veya belirgin bir kilitlenme yaşayabilir.
Bu desen, .NET Framework 1.x'te, zaman uyumlu yöntemin zaman uyumsuz BeginGetResponse/EndGetResponse çevresinde sarmalayıcı olarak uygulandığı durumu etkilemiştir.
Kılavuz: Zaman uyumlu sarmalayıcıları açığa çıkarmaktan kaçının
Zaman uyumsuz bir uygulamayı sarmalayan zaman uyumlu bir yöntemi açığa çıkarmayın. Bunun yerine, engelleme kararını tüketiciye bırakın. Tüketici iş parçacığı ortamını bilir ve bilinçli bir seçim yapabilir.
Zaman uyumsuz bir yöntemi zaman uyumlu olarak çağırmanız gerektiğini fark ederseniz, önce kodu "tamamen zaman uyumsuz" olacak şekilde yeniden yapılandırıp yapılandıramayacağınızı düşünün. Yeniden düzenleme genellikle daha iyi uzun vadeli bir çözümdür.
Senkron-üzerinden-asenkron kaçınılmaz olduğunda hafifletme stratejileri
Bazen zaman uyumsuz eşitleme gerçekten kaçınılmazdır. Örneğin, zaman uyumlu bir yöntem gerektiren bir arabirim uyguladığınızda ve tek mevcut uygulama zaman uyumsuz olduğunda bu durum kaçınılmazdır. Bu gibi durumlarda, riski azaltmak için aşağıdaki stratejileri uygulayın.
Eşzamanlı olmayan uygulamada ConfigureAwait(false) kullan
Eğer asenkron yöntemi kontrol ediyorsanız, devamın orijinal SynchronizationContext'e geri taşınmasını önlemek için her await ile Task.ConfigureAwaitfalse kullanın.
public static class ConfigureAwaitMitigation
{
public static async Task<int> LibraryMethodAsync()
{
await Task.Delay(100).ConfigureAwait(false);
return 42;
}
public static int Sync()
{
return LibraryMethodAsync().GetAwaiter().GetResult();
}
}
Public Module ConfigureAwaitMitigation
Public Async Function LibraryMethodAsync() As Task(Of Integer)
Await Task.Delay(100).ConfigureAwait(False)
Return 42
End Function
Public Function Sync() As Integer
Return LibraryMethodAsync().Result
End Function
End Module
Bir kitaplık yazarı olarak, kodunuzun yakalanan bağlamda devam etmesi gerekmediği sürece tüm await ifadelerinde ConfigureAwait(false) kullanın. Kullanmak ConfigureAwait(false) , performans için en iyi yöntemdir ve tüketiciler engellediğinde kilitlenmeleri önlemeye yardımcı olur.
İş parçacığı havuzuna boşaltma
Zaman uyumsuz uygulamayı denetlemezseniz (ve ConfigureAwait(false) kullanmıyor olabilir), çağrıyı iş parçacığı havuzuna aktarın. İş parçacığı havuzunda bir SynchronizationContext yok, bu nedenle await engellenen bir iş parçacığına yeniden taşımayı denemez:
public int Sync()
{
return Task.Run(() => Library.FooAsync()).Result;
}
Public Function Sync() As Integer
Return Task.Run(Function() Library.FooAsync()).Result
End Function
Birden çok ortamda test
Zaman uyumlu bir sarmalayıcı göndermeniz gerekiyorsa, şuradan test edin:
- Kullanıcı arabirimi (UI) iş parçacığı (WPF, Windows Forms).
- Yük altında olan iş parçacığı havuzu.
- En yüksek iş parçacığı sayısı düşük olan iş parçacığı havuzu.
- Konsol uygulaması.
Bir ortamda çalışan davranış başka bir ortamda kilitlenmeye neden olabilir.