कोशिश / पकड़ने / अंत में इंतजार करने का एक अच्छा समाधान?


92

मुझे इस तरह फिर से अपवाद (इसके स्टैक ट्रेस के साथ) को फेंकने से पहले asyncएक catchब्लॉक में एक विधि को कॉल करने की आवश्यकता है :

try
{
    // Do something
}
catch
{
    // <- Clean things here with async methods
    throw;
}

लेकिन दुर्भाग्य से आप awaitएक catchया finallyब्लॉक में उपयोग नहीं कर सकते । मैंने सीखा है क्योंकि संकलक के पास किसी भी तरह से वापस जाने के catchलिए ब्लॉक में जाने का कोई तरीका नहीं है जो आपके awaitनिर्देश के बाद है या ऐसा कुछ ...

मैंने Task.Wait()बदलने की कोशिश की awaitऔर मुझे गतिरोध मिल गया। मैंने वेब पर खोज की कि मैं इससे कैसे बच सकता हूं और यह साइट कैसे मिली ।

चूंकि मैं asyncतरीकों को बदल नहीं सकता और न ही मुझे पता है कि अगर वे उपयोग करते हैं ConfigureAwait(false), तो मैंने इन विधियों को बनाया जो Func<Task>एक अलग तरीके से शुरू होने के बाद एक async विधि शुरू करते हैं (एक गतिरोध से बचने के लिए) और इसके पूरा होने का इंतजार करते हैं:

public static void AwaitTaskSync(Func<Task> action)
{
    Task.Run(async () => await action().ConfigureAwait(false)).Wait();
}

public static TResult AwaitTaskSync<TResult>(Func<Task<TResult>> action)
{
    return Task.Run(async () => await action().ConfigureAwait(false)).Result;
}

public static void AwaitSync(Func<IAsyncAction> action)
{
    AwaitTaskSync(() => action().AsTask());
}

public static TResult AwaitSync<TResult>(Func<IAsyncOperation<TResult>> action)
{
    return AwaitTaskSync(() => action().AsTask());
}

तो मेरे सवाल हैं: क्या आपको लगता है कि यह कोड ठीक है?

बेशक, अगर आपके पास कुछ संवर्द्धन हैं या एक बेहतर दृष्टिकोण जानते हैं, तो मैं सुन रहा हूं! :)


2
awaitकैच ब्लॉक में उपयोग करना वास्तव में C # 6.0 के बाद से अनुमति है (मेरा उत्तर नीचे देखें)
आदि लेस्टर

3
संबंधित C # 5.0 त्रुटि संदेश: CS1985 : कैच क्लॉज के शरीर में प्रतीक्षा नहीं कर सकता। CS1984 : अंतत : खंड के शरीर में प्रतीक्षा नहीं कर सकता।
डेविडआरआर

जवाबों:


172

आप तर्क को catchब्लॉक से बाहर ले जा सकते हैं और यदि आवश्यक हो, तो उपयोग के बाद अपवाद को हटा सकते हैं ExceptionDispatchInfo

static async Task f()
{
    ExceptionDispatchInfo capturedException = null;
    try
    {
        await TaskThatFails();
    }
    catch (MyException ex)
    {
        capturedException = ExceptionDispatchInfo.Capture(ex);
    }

    if (capturedException != null)
    {
        await ExceptionHandler();

        capturedException.Throw();
    }
}

इस तरह, जब कॉलर अपवाद की StackTraceसंपत्ति का निरीक्षण करता है , तो यह अभी भी रिकॉर्ड करता है कि उसके अंदर कहां TaskThatFailsफेंका गया था।


1
(स्टीफन क्लीरी के जवाब में) के ExceptionDispatchInfoबजाय रखने का क्या फायदा है Exception?
वरवारा कलिनािना

2
मैं यह अनुमान लगा सकता हूं कि यदि आप पुनर्विचार करने का निर्णय लेते हैं Exception, तो आप पिछले सभी को खो देते हैं StackTrace?
वरवारा कलिनािना

1
@VarvaraKalinina बिल्कुल

54

आपको पता होना चाहिए कि सी # 6.0 के बाद से, यह उपयोग करने के लिए संभव है awaitमें catchऔर finallyब्लॉक है, तो आप वास्तव में ऐसा कर सकता है:

try
{
    // Do something
}
catch (Exception ex)
{
    await DoCleanupAsync();
    throw;
}

नई सी # 6.0 एक मैं सिर्फ उल्लेख सहित सुविधाओं, यहाँ सूचीबद्ध हैं या एक वीडियो के रूप में यहाँ


C # 6.0 में कैच / अंत में ब्लॉक का इंतजार करने का समर्थन भी विकिपीडिया पर इंगित किया गया है।
डेविड आरआर

4
@DavidRR। विकिपीडिया आधिकारिक नहीं है। जहां तक ​​यह है, यह लाखों लोगों के बीच सिर्फ एक और वेब साइट है।
user34660

जबकि यह C # 6 के लिए मान्य है, प्रश्न को शुरुआत से ही C # 5 टैग किया गया था। यह मुझे आश्चर्यचकित करता है कि क्या यह उत्तर यहाँ भ्रमित करने वाला है, या यदि हमें इन मामलों में विशिष्ट संस्करण टैग को हटा देना चाहिए।
julealgon

16

यदि आपको asyncत्रुटि संचालकों का उपयोग करने की आवश्यकता है , तो मैं इस तरह से कुछ सुझाऊंगा:

Exception exception = null;
try
{
  ...
}
catch (Exception ex)
{
  exception = ex;
}

if (exception != null)
{
  ...
}

समकालिक रूप से asyncकोड पर रोक के साथ समस्या (इस बात की परवाह किए बिना कि यह किस थ्रेड पर चल रहा है) यह है कि आप सिंक्रोनस ब्लॉक कर रहे हैं। ज्यादातर परिदृश्यों में, इसका उपयोग करना बेहतर है await

अद्यतन: चूंकि आपको पुनर्विचार करने की आवश्यकता है, इसलिए आप उपयोग कर सकते हैं ExceptionDispatchInfo


1
धन्यवाद, लेकिन दुर्भाग्य से मैं इस विधि को पहले से ही जानता हूं। यही मैं सामान्य रूप से करता हूं, लेकिन मैं यहां ऐसा नहीं कर सकता। अगर मैं केवल स्टेटमेंट throw exception;में उपयोग करता हूं ifतो स्टैक ट्रेस खो जाएगा।
user2397050

3

हमने अपनी परियोजना में निम्नलिखित पुन: प्रयोज्य उपयोगिता वर्ग के लिए hvd के शानदार उत्तर निकाले :

public static class TryWithAwaitInCatch
{
    public static async Task ExecuteAndHandleErrorAsync(Func<Task> actionAsync,
        Func<Exception, Task<bool>> errorHandlerAsync)
    {
        ExceptionDispatchInfo capturedException = null;
        try
        {
            await actionAsync().ConfigureAwait(false);
        }
        catch (Exception ex)
        {
            capturedException = ExceptionDispatchInfo.Capture(ex);
        }

        if (capturedException != null)
        {
            bool needsThrow = await errorHandlerAsync(capturedException.SourceException).ConfigureAwait(false);
            if (needsThrow)
            {
                capturedException.Throw();
            }
        }
    }
}

एक इसे निम्नानुसार उपयोग करेगा:

    public async Task OnDoSomething()
    {
        await TryWithAwaitInCatch.ExecuteAndHandleErrorAsync(
            async () => await DoSomethingAsync(),
            async (ex) => { await ShowMessageAsync("Error: " + ex.Message); return false; }
        );
    }

नामकरण में सुधार करने के लिए स्वतंत्र महसूस करें, हमने इसे जानबूझकर क्रियाशील रखा। ध्यान दें कि आवरण के अंदर के संदर्भ को पकड़ने की कोई आवश्यकता नहीं है क्योंकि यह कॉल साइट में पहले से ही कैप्चर किया गया है, इसलिए ConfigureAwait(false)

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