नए .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बस अपना सर्वश्रेष्ठ करता है, और चुपचाप विफल रहता है।