AsyncDispose में अपवादों से निपटने का उचित तरीका


20

नए .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यदि संभव हो तो क्या एक अच्छा, विहित समाधान है? इंटरनेट में मेरी खोज ने इस समस्या पर भी चर्चा नहीं की।


1
" लेकिन नेटवर्क- एक्सेसिंग क्लासेस के साथ क्लोजिंग मेथड में अपवादों को दबाने से यह बिल्कुल अच्छा नहीं लगता" - मुझे लगता है कि ज्यादातर नेटवर्कली बीएलसी क्लासेस के पास इसके लिए एक अलग Closeतरीका है। यह शायद ऐसा ही करने के लिए बुद्धिमान है: CloseAsyncचीजों को अच्छी तरह से बंद करने का प्रयास करता है और विफलता पर फेंकता है। DisposeAsyncबस अपना सर्वश्रेष्ठ करता है, और चुपचाप विफल रहता है।
छावनी 7

@ canton7: ​​खैर, एक अलग होने का CloseAsyncमतलब है कि मुझे इसे चलाने के लिए अतिरिक्त सावधानी बरतने की ज़रूरत है। अगर मैं इसे using-ब्लॉक के अंत में डाल देता हूं, तो इसे शुरुआती रिटर्न आदि पर छोड़ दिया जाएगा (यह वही है जो हम होना चाहते हैं) और अपवाद (यह वही है जो हम होना चाहते हैं)। लेकिन विचार आशाजनक लगता है।
Vlad

शुरुआती रिटर्न देने से मना करने के कारण कई कोडिंग मानक हैं। जहां नेटवर्किंग शामिल है, थोड़ा स्पष्ट होना आईएमओ की कोई बुरी बात नहीं है। Disposeहमेशा "चीजें गलत हो सकती हैं: बस स्थिति को बेहतर बनाने के लिए अपना सर्वश्रेष्ठ करें, लेकिन इसे बदतर न बनाएं", और मैं नहीं देखता कि AsyncDisposeकोई अलग क्यों होना चाहिए।
छावनी 7

@ canton7: ​​खैर, एक भाषा-अपवादों के साथ हर बयान एक प्रारंभिक वापसी हो सकती है: - \
व्लाद

सही है, लेकिन वे असाधारण होंगे । उस मामले में, DisposeAsyncअपनी पूरी कोशिश करें कि फेंकना सही हो लेकिन ऐसा करना सही नहीं है। आप जानबूझकर शुरुआती रिटर्न के बारे में बात कर रहे थे , जहां एक जानबूझकर शुरुआती रिटर्न गलत तरीके से कॉल को बायपास कर सकता है CloseAsync: वे कई कोडिंग मानकों द्वारा मना किए गए हैं।
छावनी 7

जवाबों:


3

ऐसे अपवाद हैं जिन्हें आप सतह पर लाना चाहते हैं (वर्तमान अनुरोध को बाधित करें, या प्रक्रिया को नीचे लाएं), और ऐसे अपवाद हैं कि आपके डिजाइन की उम्मीदें कभी-कभी होती हैं और आप उन्हें संभाल सकते हैं (उदाहरण के लिए पुन: प्रयास और जारी रखें)।

लेकिन इन दो प्रकारों के बीच अंतर कोड के अंतिम कॉलर तक है - यह पूरी तरह से अपवाद है, निर्णय कॉल करने वाले तक छोड़ने के लिए।

कभी-कभी कॉलर मूल कोड ब्लॉक से अपवाद को बदलने पर अधिक प्राथमिकता देगा, और कभी-कभी अपवाद से Dispose। निर्णय लेने के लिए कोई सामान्य नियम नहीं है जिसे प्राथमिकता दी जानी चाहिए। सीएलआर कम से कम सुसंगत है (जैसा कि आपने उल्लेख किया है) सिंक और गैर-एसिंक्स व्यवहार के बीच।

यह शायद दुर्भाग्यपूर्ण है कि अब हमें AggregateExceptionकई अपवादों का प्रतिनिधित्व करना होगा, इसे हल करने के लिए इसे वापस नहीं लिया जा सकता है। यदि उड़ान में पहले से ही एक अपवाद है, और दूसरा फेंक दिया जाता है, तो उन्हें एक में जोड़ दिया जाता है AggregateExceptioncatchतंत्र इतना संशोधित किया जा सकता है कि यदि आप लिखते हैं catch (MyException)तो यह किसी भी पकड़ेगा AggregateExceptionउस प्रकार के एक अपवाद भी शामिल है MyException। हालांकि इस विचार से उपजी विभिन्न जटिलताएं हैं, और अब कुछ मौलिक रूप से संशोधित करना बहुत जोखिम भरा है।

आप UsingAsyncमूल्य के शुरुआती प्रतिफल का समर्थन करने के लिए अपना सुधार कर सकते हैं :

public static async Task<R> UsingAsync<T, R>(this T disposable, Func<T, Task<R>> task)
        where T : IAsyncDisposable
{
    bool trySucceeded = false;
    R result;
    try
    {
        result = await task(disposable);
        trySucceeded = true;
    }
    finally
    {
        if (trySucceeded)
            await disposable.DisposeAsync();
        else // must suppress exceptions
            try { await disposable.DisposeAsync(); } catch { }
    }
    return result;
}

तो क्या मैं सही समझ रहा हूं: आपका विचार यह है कि कुछ मामलों में बस मानक await usingका उपयोग किया जा सकता है (यह वह जगह है जहां डिस्पोज़ेन्सी गैर-घातक मामले में नहीं फेंकेगी), और एक सहायक की तरह UsingAsyncअधिक उपयुक्त है (यदि डिस्पोज़नसिंक फेंकने की संभावना है) ? (बेशक, मुझे संशोधित करने की आवश्यकता होगी UsingAsyncताकि वह आँख बंद करके सब कुछ न पकड़ ले, लेकिन केवल गैर-घातक (और एरिक लिपर्ट के उपयोग में हड्डीरहित नहीं )।)
व्लाद

@Vlad हाँ - सही दृष्टिकोण पूरी तरह से संदर्भ पर निर्भर है। यह भी ध्यान दें कि UseAsync को एक बार अपवाद स्वरूप कुछ विश्व स्तर पर वास्तविक वर्गीकरण का उपयोग करने के लिए नहीं लिखा जा सकता है, भले ही उन्हें पकड़ा जाना चाहिए या नहीं। फिर से यह स्थिति के आधार पर अलग तरीके से लिया जाने वाला निर्णय है। जब एरिक लिपर्ट उन श्रेणियों की बात करते हैं, तो वे अपवाद प्रकारों के बारे में आंतरिक तथ्य नहीं हैं। अपवाद प्रकार की श्रेणी आपके डिज़ाइन पर निर्भर करती है। कभी-कभी IOException डिज़ाइन द्वारा अपेक्षित होता है, कभी-कभी नहीं।
डैनियल ईयरविकर

4

शायद आप पहले से ही समझते हैं कि ऐसा क्यों होता है, लेकिन यह वर्तनी के लायक है। यह व्यवहार विशिष्ट नहीं है await using। यह एक सादे usingब्लॉक के साथ भी होगा। इसलिए जब मैं Dispose()यहां कहता हूं , यह सब DisposeAsync()भी लागू होता है।

एक usingब्लॉक एक try/ finallyब्लॉक के लिए सिंटैक्टिकल चीनी है , जैसा कि प्रलेखन के अनुभाग में कहा गया है। आप जो देखते हैं वह होता है क्योंकि finallyब्लॉक हमेशा एक अपवाद के बाद भी चलता है। इसलिए यदि कोई अपवाद होता है, और कोई catchब्लॉक नहीं है, तो अपवाद को रोक दिया जाता है जब तक कि finallyब्लॉक चलता नहीं है, और फिर अपवाद को फेंक दिया जाता है। लेकिन अगर अपवाद होता है finally, तो आप पुराने अपवाद को कभी नहीं देखेंगे।

आप इसे इस उदाहरण से देख सकते हैं:

try {
    throw new Exception("Inside try");
} finally {
    throw new Exception("Inside finally");
}

इससे कोई फर्क नहीं पड़ता कि अंदर बुलाया जाता है Dispose()या नहीं । व्यवहार वही है।DisposeAsync()finally

मेरा पहला विचार है: अंदर मत फेंको Dispose()। लेकिन माइक्रोसॉफ्ट के कुछ कोड की समीक्षा करने के बाद, मुझे लगता है कि यह निर्भर करता है।

उनके कार्यान्वयन पर एक नज़र डालें FileStreamउदाहरण के लिए, । दोनों तुल्यकालिक Dispose()विधि, और DisposeAsync()वास्तव में अपवाद फेंक सकते हैं। सिंक्रोनस कुछ अपवादों को जानबूझकर अनदेखाDispose() करता है , लेकिन सभी नहीं।

लेकिन मुझे लगता है कि आपकी कक्षा की प्रकृति को ध्यान में रखना महत्वपूर्ण है। एक में FileStream, उदाहरण के लिए, Dispose()फाइल सिस्टम के लिए बफर फ्लश होगा। यह एक बहुत ही महत्वपूर्ण कार्य है और आपको यह जानने की आवश्यकता है कि क्या विफल हुआ । आप इसे अनदेखा नहीं कर सकते।

हालाँकि, अन्य प्रकार की वस्तुओं में, जब आप कॉल करते हैं, तो आपके Dispose()पास वास्तव में ऑब्जेक्ट के लिए कोई उपयोग नहीं होता है। Dispose()वास्तव में कॉल करने का अर्थ है "यह वस्तु मेरे लिए मृत है"। हो सकता है कि यह कुछ आवंटित मेमोरी को साफ कर दे, लेकिन असफल होने से आपके आवेदन के संचालन पर किसी भी तरह से कोई असर नहीं पड़ता। उस स्थिति में, आप अपने अंदर के अपवाद को अनदेखा करने का निर्णय ले सकते हैं Dispose()

लेकिन किसी भी मामले में, यदि आप एक अपवाद के बीच अंतर करना चाहते हैं using या एक अपवाद है कि से आया है Dispose(), तो आप एक की जरूरत है try/ catchके भीतर और अपने से बाहर ब्लॉक usingब्लॉक:

try {
    await using (var d = new D())
    {
        try
        {
            throw new ArgumentException("I'm inside using");
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message); // prints I'm inside using
        }
    }
} catch (Exception e) {
    Console.WriteLine(e.Message); // prints I'm inside dispose
}

या आप बस इस्तेमाल नहीं कर सकते using। बाहर लिखें try/ catch/ finallyअपने आप को ब्लॉक है, जहां आप किसी भी अपवाद को पकड़नेfinally :

var d = new D();
try
{
    throw new ArgumentException("I'm inside try");
}
catch (Exception e)
{
    Console.WriteLine(e.Message); // prints I'm inside try
}
finally
{
    try
    {
        if (D != null) await D.DisposeAsync();
    }
    catch (Exception e)
    {
        Console.WriteLine(e.Message); // prints I'm inside dispose
    }
}

3
Btw, source.dot.net (.NET Core) / referenceource.microsoft.com (.NET फ्रेमवर्क) GitHub की तुलना में ब्राउज़ करना बहुत आसान है
canton7

आपके उत्तर के लिए धन्यवाद! मुझे पता है कि वास्तविक कारण क्या है (मैंने प्रश्न में कोशिश / अंत और तुल्यकालिक मामले का उल्लेख किया है)। अब आपके प्रस्ताव के बारे में। एक catch के अंदरusing ब्लॉक होगा मदद नहीं क्योंकि आम तौर पर अपवाद हैंडलिंग कहीं दूर से किया जाता है usingब्लॉक ही है, तो यह अंदर से निपटने usingआमतौर पर नहीं बहुत साध्य है। नहीं का उपयोग करने के बारे में using- यह वास्तव में प्रस्तावित समाधान से बेहतर है?
Vlad

2
@ कैंटन 7 बहुत बढ़िया! मैं के बारे में पता था referencesource.microsoft.com , लेकिन नहीं पता था कि नेट कोर के लिए एक समान था। धन्यवाद!
गेब्रियल लुसी

@ व्लाद "बेटर" कुछ ऐसा है जिसका आप केवल उत्तर दे सकते हैं। मैं जानता हूँ कि अगर मैं किसी और के कोड पढ़ रहा था, मैं देख पसंद करेंगे try/ catch/ finallyब्लॉक के बाद से यह तुरंत स्पष्ट हो जाएगा कि क्या इसे पढ़ा क्या जाने की आवश्यकता बिना कर रहा है AsyncUsingकर रही है। आप जल्दी रिटर्न करने का विकल्प भी रखते हैं। आपके लिए एक अतिरिक्त CPU लागत भी होगी AwaitUsing। यह छोटा होगा, लेकिन यह वहाँ है।
गेब्रियल लुसी

2
@PaooMorgado इसका मतलब सिर्फ इतना है कि इसे Dispose()फेंकना नहीं चाहिए क्योंकि इसे एक से अधिक बार कहा जाता है। Microsoft के स्वयं के कार्यान्वयन अपवादों को फेंक सकते हैं, और अच्छे कारण के लिए, जैसा कि मैंने इस उत्तर में दिखाया है। हालांकि, मैं इस बात से सहमत हूं कि अगर आपको हर संभव कोशिश करनी चाहिए, तो कोई भी इसे फेंकने की उम्मीद नहीं करेगा।
गैब्रियल लुसी

4

का उपयोग करना प्रभावी रूप से अपवाद हैंडलिंग कोड है (कोशिश के लिए सिंटैक्स चीनी ... अंत में ... निपटान ())।

यदि आपका अपवाद हैंडलिंग कोड अपवादों को फेंक रहा है, तो कुछ चीज़ों का पर्दाफाश किया जाता है।

जो कुछ भी हुआ वह आपको वहां पहुंचाने के लिए करता है, वास्तव में अब और नहीं करता है। दोषपूर्ण अपवाद हैंडलिंग कोड सभी संभावित अपवादों को छिपाएगा, एक ही रास्ता या दूसरा। अपवाद हैंडलिंग कोड निश्चित होना चाहिए, जिसमें पूर्ण प्राथमिकता हो। इसके बिना, आपको वास्तविक समस्या के लिए पर्याप्त डीबगिंग डेटा कभी नहीं मिलता है। मैं देख रहा हूँ कि यह गलत तरीके से अक्सर किया जाता है। नग्न बिंदुओं को संभालना जितना आसान है, उतना ही गलत है। तो अक्सर, विषयगत I लिंक पर दो लेख होते हैं, जो आपको किसी भी अंतर्निहित डिजाइन की गलतफहमी में मदद कर सकते हैं:

अपवाद वर्गीकरण के आधार पर, यदि आपका अपवाद हैंडलिंग / डिपोस कोड एक अपवाद फेंकता है, तो आपको यह करने की आवश्यकता है:

घातक, बोनहेड और वैक्सिंग के लिए समाधान समान है।

गंभीर लागत पर भी बहिर्जात अपवादों से बचा जाना चाहिए। एक कारण है कि हम अभी भी लॉग फ़ाइलों का उपयोग करते हैं, फिर लॉग इन अपवादों को लॉग करने के लिए डेटाबेस - DB Opeartions Exogenous समस्याओं में चलाने के लिए प्रवण हैं। लॉगफ़ाइल्स एक ऐसा मामला है, जहाँ आप फ़ाइल हैंडल को पूरे रनटाइम को रखने पर भी मुझे कोई आपत्ति नहीं है।

यदि आपको एक कनेक्शन बंद करना है, तो दूसरे छोर के बारे में ज्यादा चिंता न करें। इसे संभालें जैसे कि यूडीपी करता है: "मैं जानकारी भेजूंगा, लेकिन मुझे कोई फर्क नहीं पड़ता कि दूसरा पक्ष इसे प्राप्त करता है।" डिस्पोज़ करना आपके द्वारा काम कर रहे क्लाइंट साइड / साइड पर संसाधनों की सफाई के बारे में है।

मैं उन्हें सूचित करने की कोशिश कर सकता हूं। लेकिन सर्वर / एफएस साइड पर सामान की सफाई? यही उनके टाइमआउट और उनके अपवाद से निपटने के लिए जिम्मेदार है।


तो आपका प्रस्ताव प्रभावी रूप से कनेक्शन बंद करने पर अपवादों को दबाने के लिए उबलता है, है ना?
व्लाद

@ व्लाद एक्सोजेन्स वाले? ज़रूर। डिपोज / फ़ाइनलीज़र अपनी तरफ से सफाई करने के लिए हैं। संभावना के कारण कॉनसीकॉन इंस्टेंस को बंद करते समय संभावना होती है, आप ऐसा इसलिए करते हैं क्योंकि अब आपके पास वैसे भी उनके साथ काम करने का कोई संबंध नहीं है। और पिछले "नो कनेक्शन" अपवाद को संभालने के दौरान "नो कनेक्शन" अपवाद प्राप्त करने में क्या बिंदु होगा? आप एक एकल "यो, मैं इस कनेक्शन को बंद कर रहा हूं" भेजते हैं, जहां आप सभी बहिर्जात अपवादों को अनदेखा करते हैं या भले ही वह लक्ष्य के करीब हो। Afaik डिसप्ले के डिफ़ॉल्ट कार्यान्वयन पहले से ही ऐसा करते हैं।
क्रिस्टोफर

@ व्लाद: मैंने याद किया, कि ऐसी चीजें हैं जिन्हें आप कभी भी अपवाद नहीं मानते हैं (केवल घातक लोगों को छोड़कर)। प्रकार Initliaizers सूची में हैं। निपटान भी उनमें से एक है: "यह सुनिश्चित करने में मदद करने के लिए कि संसाधनों को हमेशा उचित तरीके से साफ किया जाता है, एक अपवाद के बिना एक निपटान विधि को कई बार कॉल करने योग्य होना चाहिए।" docs.microsoft.com/en-us/dotnet/standard/garbage-collection/…
क्रिस्टोफर

@Vad घातक अपवादों की संभावना? हम लोगों को जोखिम उठाना पड़ता है और उन्हें कभी भी "कॉल डिस्पोज़" से परे नहीं रखना चाहिए। और वास्तव में उन लोगों के साथ कुछ भी नहीं करना चाहिए। वे वास्तव में किसी भी दस्तावेज में उल्लेख किए बिना जाते हैं। | अस्थिभंग अपवाद? उन्हें हमेशा ठीक करें। | Vexing अपवाद निगलने / संभालने के लिए प्रमुख उम्मीदवार हैं, जैसे TryParse () | एक्जोजिनियस? साथ ही हमेशा संभाल कर रखना चाहिए। अक्सर आप उपयोगकर्ता को उनके बारे में बताना और उन्हें लॉग इन करना भी चाहते हैं। लेकिन अन्यथा, वे आपकी प्रक्रिया को मारने के लायक नहीं हैं।
क्रिस्टोफर

@ व्लाद मैंने SqlConnection.Dispose () देखा। कनेक्शन खत्म होने के बारे में सर्वर को कुछ भी भेजने की परवाह नहीं है । परिणाम के रूप में अभी भी कुछ हो सकता हैNativeMethods.UnmapViewOfFile(); और NativeMethods.CloseHandle()। लेकिन वे बाहरी से आयात किए जाते हैं। किसी भी रिटर्न वैल्यू या किसी और चीज की कोई जाँच नहीं की जा सकती है जो कि उन दोनों के बीच एक उचित .NET एक्सेप्शन प्राप्त करने के लिए इस्तेमाल किया जा सकता है। इसलिए मैं दृढ़ता से मानता हूं, SqlConnection.Dispose (बूल) बस परवाह नहीं करता है। | क्लोज बहुत अच्छा है, वास्तव में सर्वर को बता रहा है। इससे पहले कि वह कॉल का निपटारा करे।
क्रिस्टोफर

1

आप AggregateException का उपयोग करने का प्रयास कर सकते हैं और अपने कोड को कुछ इस तरह से संशोधित कर सकते हैं:

class Program 
{
    static async Task Main()
    {
        try
        {
            await using (var d = new D())
            {
                throw new ArgumentException("I'm inside using");
            }
        }
        catch (AggregateException ex)
        {
            ex.Handle(inner =>
            {
                if (inner is Exception)
                {
                    Console.WriteLine(e.Message);
                }
            });
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
    }
}

class D : IAsyncDisposable
{
    public async ValueTask DisposeAsync()
    {
        await Task.Delay(1);
        throw new Exception("I'm inside dispose");
    }
}

https://docs.microsoft.com/ru-ru/dotnet/api/system.aggregateexception?view=netframework-4.8

https://docs.microsoft.com/ru-ru/dotnet/standard/parallel-programming/exception-handling-task-parallel-library

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.