Yeni .NET Core 3'lere geçiş sırasında IAsynsDisposable
aşağıdaki sorunla karşılaştım.
Sorunun özü: DisposeAsync
bir 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, AsyncDispose
atıldığında-istisna ve içeriden istisna await using
sadece AsyncDispose
atmazsa.
Ancak bunu başka bir şekilde tercih ederim: await using
mümkünse bloktan istisna almak ve DisposeAsync
-sadece await using
blok başarıyla bittiğinde istisna .
Gerekçe: Sınıfımın D
bazı ağ kaynaklarıyla çalıştığını ve bazı bildirimlerin uzaktan abone olduğunu hayal edin . İçindeki kod await using
yanlış 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 DisposeAsync
ilgili olanıdır. Bu, içerideki tüm istisnaları bastırmanın DisposeAsync
iyi bir fikir olmaması gerektiği anlamına gelir .
Eşzamansız durumda da aynı sorun olduğunu biliyorum: istisna finally
geç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 using
Mümkünse iyi, kanonik bir çözüm var mı ? İnternet'teki aramam bu sorunu tartışırken bile bulamadı.
CloseAsync
iç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.
Dispose
her zaman "İşler yanlış gitmiş olabilir: sadece durumu iyileştirmek için elinden geleni yap, ama daha da kötüleştirme" ve neden AsyncDispose
farklı olması gerektiğini anlamıyorum .
DisposeAsync
toparlamak 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.
Close
bu nedenle ayrı bir yöntem var düşünüyorum . Muhtemelen aynısını yapmak akıllıca olacaktır:CloseAsync
işleri güzelce kapatmaya çalışır ve başarısızlığa neden olur.DisposeAsync
sadece elinden gelenin en iyisini yapar ve sessizce başarısız olur.