नए .NET कोर 3 के लिए स्विच करने के दौरान IAsynsDisposable
, मैं निम्नलिखित समस्या पर ठोकर खाई है।
समस्या का मूल: यदि DisposeAsync
कोई अपवाद फेंकता है, तो यह अपवाद किसी अपवाद को छुपाता है जो अंदर-फेंक दिया await using
जाता है।
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");
}
}
जो पकड़ा जा रहा है, AsyncDispose
उसे फेंक दिया गया है, तो अपवाद है, और await using
केवल अंदर से अपवाद AsyncDispose
नहीं फेंकता है।
मैं फिर भी इसे अन्य तरीके से पसंद करूंगा: await using
यदि संभव हो तो ब्लॉक से अपवाद प्राप्त करना, और DisposeAsync
केवल तभी await using
ब्लॉक सफलतापूर्वक समाप्त हो गया।
तर्क: कल्पना कीजिए कि मेरी कक्षा D
कुछ नेटवर्क संसाधनों के साथ काम करती है और कुछ सूचनाओं के लिए सदस्यता लेती है। अंदर का कोड await using
कुछ गलत कर सकता है और संचार चैनल को विफल कर सकता है, इसके बाद कोड डिस्पोज में जो संचार को बंद करने की कोशिश करता है (जैसे, सूचनाओं से सदस्यता समाप्त) विफल हो जाएगा, भी। लेकिन पहला अपवाद मुझे समस्या के बारे में वास्तविक जानकारी देता है, और दूसरा केवल एक माध्यमिक समस्या है।
अन्य मामले में जब मुख्य भाग के माध्यम से भाग गया और निपटान विफल हो गया, तो वास्तविक समस्या अंदर है DisposeAsync
, इसलिए इसका अपवाद DisposeAsync
प्रासंगिक है। इसका मतलब यह है कि बस सभी अपवादों को दबाने के DisposeAsync
लिए एक अच्छा विचार नहीं होना चाहिए।
मुझे पता है कि नॉन-एसिंकस केस के साथ भी यही समस्या है: अपवाद को अपवाद में finally
ओवरराइड करता है try
, इसीलिए इसे अंदर फेंकने की अनुशंसा नहीं की जाती है Dispose()
। लेकिन नेटवर्क-एक्सेसिंग क्लासेस के साथ क्लोजिंग मेथड्स में अपवादों को दबाया जाना बिल्कुल भी अच्छा नहीं लगता।
निम्नलिखित सहायक के साथ समस्या के आसपास काम करना संभव है:
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 { }
}
}
}
और इसका उपयोग करें
await new D().UsingAsync(d =>
{
throw new ArgumentException("I'm inside using");
});
जो बदसूरत है (और ब्लॉक के अंदर शुरुआती रिटर्न जैसी चीजों को अस्वीकार करता है)।
await using
यदि संभव हो तो क्या एक अच्छा, विहित समाधान है? इंटरनेट में मेरी खोज ने इस समस्या पर भी चर्चा नहीं की।
CloseAsync
मतलब है कि मुझे इसे चलाने के लिए अतिरिक्त सावधानी बरतने की ज़रूरत है। अगर मैं इसे using
-ब्लॉक के अंत में डाल देता हूं, तो इसे शुरुआती रिटर्न आदि पर छोड़ दिया जाएगा (यह वही है जो हम होना चाहते हैं) और अपवाद (यह वही है जो हम होना चाहते हैं)। लेकिन विचार आशाजनक लगता है।
Dispose
हमेशा "चीजें गलत हो सकती हैं: बस स्थिति को बेहतर बनाने के लिए अपना सर्वश्रेष्ठ करें, लेकिन इसे बदतर न बनाएं", और मैं नहीं देखता कि AsyncDispose
कोई अलग क्यों होना चाहिए।
DisposeAsync
अपनी पूरी कोशिश करें कि फेंकना सही न हो लेकिन ऐसा करना सही नहीं है। आप जानबूझकर शुरुआती रिटर्न के बारे में बात कर रहे थे , जहां एक जानबूझकर शुरुआती रिटर्न गलत तरीके से कॉल को बायपास कर सकता है CloseAsync
: वे कई कोडिंग मानकों द्वारा मना किए गए हैं।
Close
तरीका है। यह शायद ऐसा ही करने के लिए बुद्धिमान है:CloseAsync
चीजों को अच्छी तरह से बंद करने का प्रयास करता है और विफलता पर फेंकता है।DisposeAsync
बस अपना सर्वश्रेष्ठ करता है, और चुपचाप विफल रहता है।