कैसे बाहर पैरामीटर के साथ एक async विधि लिखने के लिए?


176

मैं एक outपैरामीटर के साथ एक async विधि लिखना चाहता हूं , जैसे:

public async void Method1()
{
    int op;
    int result = await GetDataTaskAsync(out op);
}

मैं इसमें कैसे करूँ GetDataTaskAsync?

जवाबों:


279

आपके पास refया outमापदंडों के साथ async विधियाँ नहीं हो सकती हैं ।

Lucian Wischik बताते हैं कि इस MSDN थ्रेड पर यह क्यों संभव नहीं है: http://social.msdn.microsoft.com/Forums/en-US/d2f48a52-e35a-4948-844d-28a1a6deb74/why-async-methods-cannot-have -ref या बाहर-पैरामीटर

के लिए के रूप में क्यों async तरीकों संदर्भ के बाहर मापदंडों का समर्थन नहीं करते? (या रेफरी पैरामीटर?) यह सीएलआर की एक सीमा है। हमने async विधियों को पुनरावृत्ति विधियों के समान तरीके से कार्यान्वित करने के लिए चुना - अर्थात संकलक के माध्यम से विधि को राज्य-मशीन-ऑब्जेक्ट में परिवर्तित करना। सीएलआर के पास ऑब्जेक्ट के क्षेत्र के रूप में "आउट पैरामीटर" या "संदर्भ पैरामीटर" के पते को संग्रहीत करने का कोई सुरक्षित तरीका नहीं है। आउट-बाय-रेफरेंस मापदंडों का समर्थन करने का एकमात्र तरीका यह होगा कि कंपाइलर-राईटाइट के बजाए निम्न-स्तरीय सीएलआर पुनर्लेखन द्वारा एस्किंक फीचर किया जाए। हमने उस दृष्टिकोण की जांच की, और इसके लिए बहुत कुछ हो रहा था, लेकिन यह अंततः इतना महंगा हो गया था कि ऐसा कभी नहीं हुआ होगा।

इस स्थिति के लिए एक विशिष्ट समाधान के लिए async विधि के बजाय एक Tuple वापस करना है। आप अपनी विधि को इस प्रकार पुनः लिख सकते हैं:

public async Task Method1()
{
    var tuple = await GetDataTaskAsync();
    int op = tuple.Item1;
    int result = tuple.Item2;
}

public async Task<Tuple<int, int>> GetDataTaskAsync()
{
    //...
    return new Tuple<int, int>(1, 2);
}

10
बहुत अधिक जटिल होने से, यह बहुत अधिक समस्या पैदा कर सकता है। जॉन स्कीट यह बहुत अच्छी तरह से यहाँ समझाया stackoverflow.com/questions/20868103/...
MuiBienCarlota

3
Tupleविकल्प के लिए धन्यवाद । बहुत मददगार।
ल्यूक Vo

19
यह बदसूरत है Tuple। : पी
टोफुटिम

36
मुझे लगता है कि C # 7 में नामांकित ट्यूपल इसके लिए सही समाधान होगा।
उड़द

3
@ मैं इसे विशेष रूप से पसंद करता हूं: निजी async टास्क <(bool success, Job job, string message)> TryGetJobAsync (...)
जे एंड्रयू लाफलिन

51

आपके पास refया विधियों outमें पैरामीटर नहीं हो सकते हैं async(जैसा कि पहले ही नोट किया गया था)।

यह चारों ओर घूम रहे डेटा में कुछ मॉडलिंग के लिए चिल्लाती है:

public class Data
{
    public int Op {get; set;}
    public int Result {get; set;}
}

public async void Method1()
{
    Data data = await GetDataTaskAsync();
    // use data.Op and data.Result from here on
}

public async Task<Data> GetDataTaskAsync()
{
    var returnValue = new Data();
    // Fill up returnValue
    return returnValue;
}

आप अपने कोड को अधिक आसानी से पुन: उपयोग करने की क्षमता प्राप्त करते हैं, साथ ही यह चर या ट्यूपल की तुलना में अधिक पठनीय है।


2
मैं एक ट्यूपल का उपयोग करने के बजाय इस समाधान को पसंद करता हूं। अधिक साफ!
MiBol

31

C # 7 + समाधान निहित टुपल सिंटैक्स का उपयोग करना है।

    private async Task<(bool IsSuccess, IActionResult Result)> TryLogin(OpenIdConnectRequest request)
    { 
        return (true, BadRequest(new OpenIdErrorResponse
        {
            Error = OpenIdConnectConstants.Errors.AccessDenied,
            ErrorDescription = "Access token provided is not valid."
        }));
    }

वापसी परिणाम विधि हस्ताक्षर परिभाषित संपत्ति के नाम का उपयोग करता है। उदाहरण के लिए:

var foo = await TryLogin(request);
if (foo.IsSuccess)
     return foo.Result;

12

एलेक्स ने पठनीयता पर एक महान बिंदु बनाया। समान रूप से, एक फ़ंक्शन भी इंटरफ़ेस को परिभाषित करने के लिए पर्याप्त है जो लौटाया जा रहा है और आपको सार्थक चर नाम भी मिलते हैं।

delegate void OpDelegate(int op);
Task<bool> GetDataTaskAsync(OpDelegate callback)
{
    bool canGetData = true;
    if (canGetData) callback(5);
    return Task.FromResult(canGetData);
}

कॉल करने वालों को एक लैम्बडा (या एक नामांकित फ़ंक्शन) प्रदान करता है और प्रतिनिधि से चर नाम (ओं) को कॉपी करके इंटैलिजेंस मदद करता है।

int myOp;
bool result = await GetDataTaskAsync(op => myOp = op);

यह विशेष दृष्टिकोण एक "कोशिश" विधि की तरह है जहां myOpविधि परिणाम होने पर सेट किया जाता है true। अन्यथा, आप परवाह नहीं करते हैं myOp


9

outमापदंडों की एक अच्छी विशेषता यह है कि उनका उपयोग डेटा वापस करने के लिए भी किया जा सकता है जब कोई फ़ंक्शन अपवाद फेंकता है। मुझे लगता है कि asyncविधि के साथ ऐसा करने के लिए निकटतम समकक्ष डेटा को धारण करने के लिए एक नई वस्तु का उपयोग कर रहा होगा जिसे asyncविधि और कॉलर दोनों संदर्भित कर सकते हैं। एक अन्य तरीका एक प्रतिनिधि को पारित करना होगा जैसा कि दूसरे उत्तर में सुझाया गया है

ध्यान दें कि इन तकनीकों में से किसी में भी संकलक से किसी भी प्रकार का प्रवर्तन नहीं होगा out। यानी, संकलक को आपको साझा किए गए ऑब्जेक्ट पर मान सेट करने या प्रतिनिधि में एक पास कॉल करने की आवश्यकता नहीं होगी।

एक साझा ऑब्जेक्ट का उपयोग करने के लिए एक उदाहरण कार्यान्वयन है, जहां तरीकों और अन्य विभिन्न परिदृश्यों के लिए नकल करने refऔर outउपयोग करने के लिए asyncजहां refऔर outउपलब्ध नहीं हैं:

class Ref<T>
{
    // Field rather than a property to support passing to functions
    // accepting `ref T` or `out T`.
    public T Value;
}

async Task OperationExampleAsync(Ref<int> successfulLoopsRef)
{
    var things = new[] { 0, 1, 2, };
    var i = 0;
    while (true)
    {
        // Fourth iteration will throw an exception, but we will still have
        // communicated data back to the caller via successfulLoopsRef.
        things[i] += i;
        successfulLoopsRef.Value++;
        i++;
    }
}

async Task UsageExample()
{
    var successCounterRef = new Ref<int>();
    // Note that it does not make sense to access successCounterRef
    // until OperationExampleAsync completes (either fails or succeeds)
    // because there’s no synchronization. Here, I think of passing
    // the variable as “temporarily giving ownership” of the referenced
    // object to OperationExampleAsync. Deciding on conventions is up to
    // you and belongs in documentation ^^.
    try
    {
        await OperationExampleAsync(successCounterRef);
    }
    finally
    {
        Console.WriteLine($"Had {successCounterRef.Value} successful loops.");
    }
}

6

मुझे Tryपैटर्न पसंद है। यह एक साफ पैटर्न है।

if (double.TryParse(name, out var result))
{
    // handle success
}
else
{
    // handle error
}

लेकिन, इसके साथ चुनौतीपूर्ण है async । इसका मतलब यह नहीं है कि हमारे पास वास्तविक विकल्प नहीं हैं। यहाँ तीन कोर दृष्टिकोण हैं जो आप पैटर्न के asyncअर्ध-संस्करण में तरीकों के लिए विचार कर सकते हैं Try

दृष्टिकोण 1 - एक संरचना का उत्पादन

यह एक सिंक Tryविधि की तरह दिखता है, केवल एक के tupleबजाय एक के boolसाथ लौट रहा हैout पैरामीटर है, जो हम सब पता है कि सी # में अनुमति नहीं है।

var result = await DoAsync(name);
if (result.Success)
{
    // handle success
}
else
{
    // handle error
}

एक ऐसी विधि के साथ जो रिटर्न करती trueहैfalse और कभी भी किसी फेंकता है exception

याद रखें, एक अपवाद को एक Tryविधि में फेंकने से पैटर्न का पूरा उद्देश्य टूट जाता है।

async Task<(bool Success, StorageFile File, Exception exception)> DoAsync(string fileName)
{
    try
    {
        var folder = ApplicationData.Current.LocalCacheFolder;
        return (true, await folder.GetFileAsync(fileName), null);
    }
    catch (Exception exception)
    {
        return (false, null, exception);
    }
}

दृष्टिकोण 2 - कॉलबैक विधियों में पास करें

हम anonymousबाहरी चर सेट करने के तरीकों का उपयोग कर सकते हैं । यह चालाक वाक्यविन्यास है, हालांकि थोड़ा जटिल है। छोटी खुराक में, यह ठीक है।

var file = default(StorageFile);
var exception = default(Exception);
if (await DoAsync(name, x => file = x, x => exception = x))
{
    // handle success
}
else
{
    // handle failure
}

Tryपद्धति पैटर्न की मूल बातों का पालन करती है लेकिन outकॉलबैक विधियों में पारित करने के लिए पैरामीटर सेट करती है । यह इस तरह किया जाता है।

async Task<bool> DoAsync(string fileName, Action<StorageFile> file, Action<Exception> error)
{
    try
    {
        var folder = ApplicationData.Current.LocalCacheFolder;
        file?.Invoke(await folder.GetFileAsync(fileName));
        return true;
    }
    catch (Exception exception)
    {
        error?.Invoke(exception);
        return false;
    }
}

यहां प्रदर्शन के बारे में मेरे मन में एक सवाल है। लेकिन, C # कंपाइलर इतना भयावह स्मार्ट है, कि मुझे लगता है कि आप सुरक्षित हैं इस विकल्प को चुनना, लगभग सुनिश्चित करना।

दृष्टिकोण 3 - ContinueWith का उपयोग करें

क्या होगा यदि आप बस के TPLरूप में डिजाइन का उपयोग करें ? कोई तुक नहीं। यहां विचार यह है कि हम अपवादों का उपयोग ContinueWithदो अलग-अलग रास्तों पर पुनर्निर्देशित करने के लिए करते हैं।

await DoAsync(name).ContinueWith(task =>
{
    if (task.Exception != null)
    {
        // handle fail
    }
    if (task.Result is StorageFile sf)
    {
        // handle success
    }
});

एक ऐसी विधि के साथ जो exceptionकिसी भी प्रकार की विफलता होने पर फेंकता है। यह एक लौटने से अलग है boolean। यह एक तरह से संवाद है TPL

async Task<StorageFile> DoAsync(string fileName)
{
    var folder = ApplicationData.Current.LocalCacheFolder;
    return await folder.GetFileAsync(fileName);
}

उपरोक्त कोड में, यदि फ़ाइल नहीं मिली है, तो एक अपवाद फेंक दिया गया है। यह उस विफलता का आह्वान ContinueWithकरेगा Task.Exceptionजो इसके लॉजिक ब्लॉक में काम करेगी। नीट, हुह?

सुनो, एक कारण है कि हम Tryपैटर्न से प्यार करते हैं । यह मूल रूप से इतना साफ और पठनीय है और, परिणामस्वरूप, बनाए रखने योग्य है। जैसा कि आप अपना दृष्टिकोण चुनते हैं, पठनीयता के लिए वॉचडॉग। अगले डेवलपर को याद रखें जो 6 महीने में है और आपके पास स्पष्ट सवालों के जवाब देने के लिए नहीं है। आपका कोड एकमात्र दस्तावेज हो सकता है जो एक डेवलपर के पास होगा।

शुभकामनाएँ।


1
तीसरे दृष्टिकोण के बारे में, क्या आप सुनिश्चित हैं कि ContinueWithकॉलिंग कॉल के अपेक्षित परिणाम हैं? मेरी समझ के अनुसार दूसरा ContinueWithपहली निरंतरता की सफलता की जांच करेगा, मूल कार्य की सफलता की नहीं।
थियोडोर जूलियास

1
चीयर्स @TheodorZoulias, यह एक तेज आंख है। फिक्स्ड।
जैरी निक्सन

1
प्रवाह नियंत्रण के लिए अपवाद फेंकना मेरे लिए एक बड़े पैमाने पर कोड गंध है - यह आपके प्रदर्शन को टैंक करने वाला है।
इयान केम्प

नहीं, @IanKemp, यह एक बहुत पुरानी अवधारणा है। संकलक विकसित हुआ है।
जेरी निक्सन

4

मुझे भी यही समस्या थी कि मैं ट्राई-मेथड-पैटर्न का उपयोग करना पसंद करता हूं जो मूल रूप से एसिंक्स-वेट-प्रतिमान के लिए असंगत लगता है ...

मेरे लिए महत्वपूर्ण यह है कि मैं एक एकल-खंड के भीतर ट्राय-मेथड को कॉल कर सकता हूं और इससे पहले आउट-वेरिएबल्स को पूर्व-परिभाषित करने की आवश्यकता नहीं है, लेकिन इसे निम्न उदाहरण की तरह इन-लाइन कर सकते हैं:

if (TryReceive(out string msg))
{
    // use msg
}

तो मैं निम्नलिखित समाधान के साथ आया:

  1. एक सहायक संरचना को परिभाषित करें:

     public struct AsyncOut<T, OUT>
     {
         private readonly T returnValue;
         private readonly OUT result;
    
         public AsyncOut(T returnValue, OUT result)
         {
             this.returnValue = returnValue;
             this.result = result;
         }
    
         public T Out(out OUT result)
         {
             result = this.result;
             return returnValue;
         }
    
         public T ReturnValue => returnValue;
    
         public static implicit operator AsyncOut<T, OUT>((T returnValue ,OUT result) tuple) => 
             new AsyncOut<T, OUT>(tuple.returnValue, tuple.result);
     }
  2. इस तरह से async कोशिश-विधि को परिभाषित करें:

     public async Task<AsyncOut<bool, string>> TryReceiveAsync()
     {
         string message;
         bool success;
         // ...
         return (success, message);
     }
  3. इस तरह async कोशिश-विधि को बुलाओ:

     if ((await TryReceiveAsync()).Out(out string msg))
     {
         // use msg
     }

कई आउट पैरामीटर के लिए आप अतिरिक्त संरचनाएं (जैसे AsyncOut <T, OUT1, OUT2>) को परिभाषित कर सकते हैं या आप एक टपल लौटा सकते हैं।


यह एक बहुत ही चतुर उपाय है!
थियोडोर जूलियास

2

मापदंडों को asyncस्वीकार नहीं करने वाले तरीकों की सीमा outकेवल कंपाइलर-जेनरेट किए गए एसिंक्स तरीकों पर लागू होती है, इन्हें asyncकीवर्ड के साथ घोषित किया जाता है । यह हाथ से तैयार किए गए async तरीकों पर लागू नहीं होता है। दूसरे शब्दों में मापदंडों को Taskस्वीकार करते हुए वापसी के तरीके बनाना संभव है out। उदाहरण के लिए हम कहते हैं कि हमारे पास पहले से ही एक ParseIntAsyncविधि है जो फेंकता है, और हम एक TryParseIntAsyncऐसा बनाना चाहते हैं जो फेंक नहीं देता है। हम इसे इस तरह लागू कर सकते हैं:

public static Task<bool> TryParseIntAsync(string s, out Task<int> result)
{
    var tcs = new TaskCompletionSource<int>();
    result = tcs.Task;
    return ParseIntAsync(s).ContinueWith(t =>
    {
        if (t.IsFaulted)
        {
            tcs.SetException(t.Exception.InnerException);
            return false;
        }
        tcs.SetResult(t.Result);
        return true;
    }, default, TaskContinuationOptions.None, TaskScheduler.Default);
}

विधि TaskCompletionSourceऔर ContinueWithविधि का उपयोग करना थोड़ा अजीब है, लेकिन कोई अन्य विकल्प नहीं है क्योंकि हम सुविधाजनक का उपयोग नहीं कर सकते हैंawait इस पद्धति के अंदर कीवर्ड का ।

उपयोग उदाहरण:

if (await TryParseIntAsync("-13", out var result))
{
    Console.WriteLine($"Result: {await result}");
}
else
{
    Console.WriteLine($"Parse failed");
}

अद्यतन: यदि एसिंक्स तर्क बिना व्यक्त किए जाने के लिए बहुत जटिल है await, तो इसे एक नेस्टेड अतुल्यकालिक प्रतिनिधि प्रतिनिधि के अंदर समझाया जा सकता है। एक TaskCompletionSourceअभी भी के लिए जरूरत होगी outपैरामीटर। यह संभव है कि outपैरामीटर मुख्य कार्य के पूरा होने से पहले पूरा किया जा सकता है, जैसा कि उदाहरण bellow में:

public static Task<string> GetDataAsync(string url, out Task<int> rawDataLength)
{
    var tcs = new TaskCompletionSource<int>();
    rawDataLength = tcs.Task;
    return ((Func<Task<string>>)(async () =>
    {
        var response = await GetResponseAsync(url);
        var rawData = await GetRawDataAsync(response);
        tcs.SetResult(rawData.Length);
        return await FilterDataAsync(rawData);
    }))();
}

यह उदाहरण तीन अतुल्यकालिक तरीकों के अस्तित्व को मानता है GetResponseAsync, GetRawDataAsyncऔर FilterDataAsyncजिसे उत्तराधिकार में कहा जाता है। outपैरामीटर दूसरी विधि के पूरा होने पर पूरा हो गया है। GetDataAsyncविधि इस तरह इस्तेमाल किया जा सकता:

var data = await GetDataAsync("http://example.com", out var rawDataLength);
Console.WriteLine($"Data: {data}");
Console.WriteLine($"RawDataLength: {await rawDataLength}");

प्रतीक्षा करने dataसे पहले प्रतीक्षा करना rawDataLengthइस सरलीकृत उदाहरण में महत्वपूर्ण है, क्योंकि अपवाद के मामले में outपैरामीटर कभी पूरा नहीं होगा।


1
यह कुछ मामलों के लिए एक बहुत अच्छा समाधान है।
जेरी निक्सन

1

मुझे लगता है कि इस तरह से ValueTuples का उपयोग करना काम कर सकता है। आपको पहले हालांकि ValueTuple NuGet पैकेज जोड़ना होगा:

public async void Method1()
{
    (int op, int result) tuple = await GetDataTaskAsync();
    int op = tuple.op;
    int result = tuple.result;
}

public async Task<(int op, int result)> GetDataTaskAsync()
{
    int x = 5;
    int y = 10;
    return (op: x, result: y):
}

.Net-4.7 या netstandard-2.0 का उपयोग करते समय आपको NuGet की आवश्यकता नहीं है।
बिंकी

अरे, तुम सही हो! मैंने अभी उस NuGet पैकेज की स्थापना रद्द की है और यह अभी भी काम करता है। धन्यवाद!
पॉल मारंगोनी

1

यहाँ नाम के कोड @ 7.0 के लिए C # 7.0 के लिए संशोधित किया गया है जिसका नाम tuples और tuple deconstruction है, जो अंकन को सुव्यवस्थित करता है:

public async void Method1()
{
    // Version 1, named tuples:
    // just to show how it works
    /*
    var tuple = await GetDataTaskAsync();
    int op = tuple.paramOp;
    int result = tuple.paramResult;
    */

    // Version 2, tuple deconstruction:
    // much shorter, most elegant
    (int op, int result) = await GetDataTaskAsync();
}

public async Task<(int paramOp, int paramResult)> GetDataTaskAsync()
{
    //...
    return (1, 2);
}

टुपल्स, टुपल लिटरल और टपल डेकोस्ट्रक्शन के नए नाम के बारे में जानकारी के लिए देखें: https://blogs.msdn.microsoft.com/dotnet/2017/03/09/new-features-in-c-7-0/


-2

आप प्रतीक्षित कीवर्ड का उपयोग करते हुए प्रत्यक्ष के बजाय टीपीएल (टास्क समानांतर लाइब्रेरी) का उपयोग करके ऐसा कर सकते हैं।

private bool CheckInCategory(int? id, out Category category)
    {
        if (id == null || id == 0)
            category = null;
        else
            category = Task.Run(async () => await _context.Categories.FindAsync(id ?? 0)).Result;

        return category != null;
    }

if(!CheckInCategory(int? id, out var category)) return error

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