Yeni .NET Core 3'lere geçiş sırasında IAsynsDisposableaşağıdaki sorunla karşılaştım.
Sorunun özü: DisposeAsyncbir istisna atarsa, bu istisna await using-block içine atılan istisnaları gizler .
class Program
{
static async Task Main()
{
try
{
await using (var d = new D())
{
throw new ArgumentException("I'm inside using");
}
}
catch (Exception e)
{
Console.WriteLine(e.Message); // prints I'm inside dispose
}
}
}
class D : IAsyncDisposable
{
public async ValueTask DisposeAsync()
{
await Task.Delay(1);
throw new Exception("I'm inside dispose");
}
}
Yakalanan şey, AsyncDisposeatıldığında-istisna ve içeriden istisna await usingsadece AsyncDisposeatmazsa.
Ancak bunu başka bir şekilde tercih ederim: await usingmümkünse bloktan istisna almak ve DisposeAsync-sadece await usingblok başarıyla bittiğinde istisna .
Gerekçe: Sınıfımın Dbazı ağ kaynaklarıyla çalıştığını ve bazı bildirimlerin uzaktan abone olduğunu hayal edin . İçindeki kod await usingyanlış bir şey yapabilir ve iletişim kanalında başarısız olabilir, bundan sonra Dispose'deki iletişimi zarif bir şekilde kapatmaya çalışan kod (örneğin, bildirimlerden çıkma) da başarısız olur. Ama ilk istisna bana sorunla ilgili gerçek bilgileri veriyor, ikincisi ise sadece ikincil bir problem.
Diğer durumda, ana kısım bittiğinde ve bertaraf başarısız olduğunda, gerçek sorun içeridedir DisposeAsync, bu nedenle istisna DisposeAsyncilgili olanıdır. Bu, içerideki tüm istisnaları bastırmanın DisposeAsynciyi bir fikir olmaması gerektiği anlamına gelir .
Eşzamansız durumda da aynı sorun olduğunu biliyorum: istisna finallygeçersiz kılınır try, bu yüzden atmak tavsiye edilmez Dispose(). Ancak ağa erişen sınıflarla, kapatma yöntemlerindeki istisnaları bastırmak hiç de iyi görünmüyor.
Aşağıdaki yardımcıyla soruna geçici bir çözüm bulmak mümkündür:
static class AsyncTools
{
public static async Task UsingAsync<T>(this T disposable, Func<T, Task> task)
where T : IAsyncDisposable
{
bool trySucceeded = false;
try
{
await task(disposable);
trySucceeded = true;
}
finally
{
if (trySucceeded)
await disposable.DisposeAsync();
else // must suppress exceptions
try { await disposable.DisposeAsync(); } catch { }
}
}
}
ve onu gibi kullan
await new D().UsingAsync(d =>
{
throw new ArgumentException("I'm inside using");
});
ki bu çirkin bir şeydir (ve kullanım bloğundaki erken dönüşler gibi şeylere izin vermez).
await usingMümkünse iyi, kanonik bir çözüm var mı ? İnternet'teki aramam bu sorunu tartışırken bile bulamadı.
CloseAsynciçin ekstra önlemler almam gereken ayrı bir yol var . Eğer sadece using-block'un sonuna koyarsam, erken iadelerde vb. Ama fikir umut verici görünüyor.
Disposeher zaman "İşler yanlış gitmiş olabilir: sadece durumu iyileştirmek için elinden geleni yap, ama daha da kötüleştirme" ve neden AsyncDisposefarklı olması gerektiğini anlamıyorum .
DisposeAsynctoparlamak için elinden gelenin en iyisini yapmak, ancak atmamak doğru şeydir. Kasıtlı erken dönüşlerden bahsediyordunuz , kasıtlı erken dönüş yanlışlıkla bir çağrıyı yanlışlıkla atlayabilir CloseAsync: bunlar birçok kodlama standardı tarafından yasaklananlardır.
Closebu nedenle ayrı bir yöntem var düşünüyorum . Muhtemelen aynısını yapmak akıllıca olacaktır:CloseAsyncişleri güzelce kapatmaya çalışır ve başarısızlığa neden olur.DisposeAsyncsadece elinden gelenin en iyisini yapar ve sessizce başarısız olur.