"टास्क का इंतजार करें। () के बीच कोई अंतर; वापसी;" और "रिटर्न टास्क.ऑन ()"?


90

क्या कोड के निम्नलिखित दो टुकड़ों के बीच कोई वैचारिक अंतर है:

async Task TestAsync() 
{
    await Task.Run(() => DoSomeWork());
}

तथा

Task TestAsync() 
{
    return Task.Run(() => DoSomeWork());
}

क्या उत्पन्न कोड अलग है, या तो?

संपादित करें:Task.Run एक समान मामले के साथ भ्रम से बचने के लिए :

async Task TestAsync() 
{
    await Task.Delay(1000);
}

तथा

Task TestAsync() 
{
    return Task.Delay(1000);
}

LATE UPDATE: स्वीकृत उत्तर के अलावा, इसमें एक अंतर यह भी है कि कैसे LocalCallContextसंभाला जाता है: CallContext। क्यों?


1
हाँ, यह अलग है। और यह बहुत अलग है। अन्यथा उपयोग करने का कोई मतलब नहीं होगा await/ async:) :)
MarcinJuraszek

1
मुझे लगता है कि यहां दो सवाल हैं। 1. क्या इसके कॉलर को विधि का वास्तविक कार्यान्वयन मायने रखता है? 2. क्या दो विधियों के संकलित निरूपण में अंतर है?
डेविड आरआर

जवाबों:


80

अपवाद प्रचार में एक बड़ा अंतर है एक अपवाद, एक अंदर फेंक दिया async Taskविधि, लौटे में संग्रहीत हो जाता है Taskवस्तु और निष्क्रिय बनी हुई है जब तक काम के माध्यम से मनाया जाता है await task, task.Wait(), task.Resultया task.GetAwaiter().GetResult()। विधि के समकालिक भाग से फेंके जाने पर भी यह इस तरह से प्रचारित होता है async

निम्नलिखित कोड पर विचार करें, जहां OneTestAsyncऔर AnotherTestAsyncअलग तरह से व्यवहार करते हैं:

static async Task OneTestAsync(int n)
{
    await Task.Delay(n);
}

static Task AnotherTestAsync(int n)
{
    return Task.Delay(n);
}

// call DoTestAsync with either OneTestAsync or AnotherTestAsync as whatTest
static void DoTestAsync(Func<int, Task> whatTest, int n)
{
    Task task = null;
    try
    {
        // start the task
        task = whatTest(n);

        // do some other stuff, 
        // while the task is pending
        Console.Write("Press enter to continue");
        Console.ReadLine();
        task.Wait();
    }
    catch (Exception ex)
    {
        Console.Write("Error: " + ex.Message);
    }
}

अगर मैं फोन DoTestAsync(OneTestAsync, -2)करता हूं , तो यह निम्नलिखित आउटपुट का उत्पादन करता है:

जारी रखने के लिए एंटर दबाएं
त्रुटि: एक या अधिक त्रुटियां हुईं
त्रुटि: २

ध्यान दें, मुझे Enterइसे देखने के लिए प्रेस करना था ।

अब, अगर मैं कॉल करता हूं, तो DoTestAsync(AnotherTestAsync, -2)अंदर कोड वर्कफ़्लो DoTestAsyncकाफी अलग है, और इसलिए आउटपुट है। इस बार, मुझे प्रेस करने के लिए नहीं कहा गया था Enter:

त्रुटि: मान या तो -1 होना चाहिए (एक अनंत टाइमआउट को दर्शाता है), 0 या एक सकारात्मक पूर्णांक।
पैरामीटर नाम: मिलीसेकंडडेलएयर: 1

दोनों मामलों में Task.Delay(-2)अपने मापदंडों को मान्य करते हुए, शुरुआत में फेंकता है। यह एक बना-बनाया परिदृश्य हो सकता है, लेकिन सिद्धांत में Task.Delay(1000), उदाहरण के लिए, अंतर्निहित सिस्टम टाइमर API के विफल होने पर भी फेंक सकता है।

साइड नोट पर, त्रुटि प्रसार तर्क async voidविधियों के लिए अभी तक भिन्न है (जैसा कि async Taskविधियों के विपरीत )। किसी async voidविधि के अंदर उठाया गया अपवाद वर्तमान थ्रेड के सिंक्रनाइज़ेशन संदर्भ (थ्रू SynchronizationContext.Post) पर तुरंत फिर से फेंक दिया जाएगा , यदि वर्तमान थ्रेड में एक है ( SynchronizationContext.Current != null)अन्यथा, अन्यथा, इसे फिर से फेंक दिया जाएगा ThreadPool.QueueUserWorkItem)। कॉलर के पास एक ही स्टैक फ्रेम पर इस अपवाद को संभालने का मौका नहीं है।

मैंने यहाँ और यहाँ TPL अपवाद हैंडलिंग व्यवहार के बारे में कुछ और विवरण पोस्ट किए हैं


प्रश्न : क्या asyncगैर-एसिंक्स- Taskआधारित विधियों के लिए तरीकों के अपवाद प्रसार व्यवहार की नकल करना संभव है , ताकि बाद वाला समान स्टैक फ्रेम पर न फेंके?

एक : अगर वास्तव में जरूरत है, तो हाँ, इसके लिए एक चाल है:

// async
async Task<int> MethodAsync(int arg)
{
    if (arg < 0)
        throw new ArgumentException("arg");
    // ...
    return 42 + arg;
}

// non-async
Task<int> MethodAsync(int arg)
{
    var task = new Task<int>(() => 
    {
        if (arg < 0)
            throw new ArgumentException("arg");
        // ...
        return 42 + arg;
    });

    task.RunSynchronously(TaskScheduler.Default);
    return task;
}

हालाँकि, कुछ शर्तों के तहत ध्यान दें (जैसे कि यह स्टैक पर बहुत गहरा है), RunSynchronouslyफिर भी अतुल्यकालिक रूप से निष्पादित कर सकता है।


एक और उल्लेखनीय अंतर यह है कि है / संस्करण अधिक मृत-लॉक एक गैर-डिफ़ॉल्ट तुल्यकालन संदर्भ पर की संभावना है । उदाहरण के लिए, WinForms या WPF एप्लिकेशन में निम्नलिखित डेड-लॉक होगा:asyncawait

static async Task TestAsync()
{
    await Task.Delay(1000);
}

void Form_Load(object sender, EventArgs e)
{
    TestAsync().Wait(); // dead-lock here
}

इसे नॉन-एस्किंट वर्जन में बदलें और यह डेड-लॉक नहीं होगा:

Task TestAsync() 
{
    return Task.Delay(1000);
}

मृत-लॉक की प्रकृति को स्टीफन क्लीरी ने अपने ब्लॉग में अच्छी तरह से समझाया है ।


2
मेरा मानना ​​है कि पहले उदाहरण में गतिरोध को जोड़कर टाला जा सकता है ।onfigureAwait (झूठी) प्रतीक्षा लाइन में, क्योंकि यह केवल इसलिए होता है क्योंकि विधि उसी निष्पादन संदर्भ में लौटने की कोशिश कर रही है। तो फिर छूट केवल अंतर है जो बनी हुई है।
अपेक्षाकृत_रजेंसी

2
@relatively_random, आपकी टिप्पणी सही है, हालांकि उत्तर के बीच अंतर के बारे में था return Task.Run()और await Task.Run(); returnइसके बजायawait Task.Run().ConfigureAwait(false); return
noseratio

यदि आप Enter को हिट करने के बाद प्रोग्राम बंद करते हैं, तो सुनिश्चित करें कि आप F5 के बजाय ctrl + F5 करते हैं।
डेविड क्लेम्फनर

54

दोनों के बीच क्या अंतर है

async Task TestAsync() 
{
    await Task.Delay(1000);
}

तथा

Task TestAsync() 
{
    return Task.Delay(1000);
}

?

मैं इस सवाल से भ्रमित हूं। मुझे अपने प्रश्न का दूसरे प्रश्न के साथ उत्तर देकर स्पष्ट करने का प्रयास करें। के बीच क्या अंतर है?

Func<int> MakeFunction()
{
    Func<int> f = ()=>1;
    return ()=>f();
}

तथा

Func<int> MakeFunction()
{
    return ()=>1;
}

?

मेरी दोनों चीजों में जो भी अंतर है, वही अंतर आपकी दोनों चीजों में है।


22
बेशक! आपने अपनी आँखें खोली हैं :) पहले मामले में, मैं एक आवरण कार्य बनाता हूं, शब्दार्थ के करीब Task.Delay(1000).ContinueWith(() = {})। दूसरे में, यह सिर्फ है Task.Delay(1000)। अंतर कुछ सूक्ष्म है, लेकिन महत्वपूर्ण है।
एवो

3
क्या आप थोड़ा अंतर बता सकते हैं? वास्तव में मैं नहीं..धन्यवाद
zheng यू

4
यह देखते हुए कि सिंक संदर्भों के साथ एक सूक्ष्म अंतर है, और अपवाद प्रचार मैं कहूंगा कि async / प्रतीक्षा और फ़ंक्शन रैपर के बीच का अंतर समान नहीं है।
कैमरन मैकफारलैंड

1
@CameronMacFarland: यही कारण है कि मैंने स्पष्टीकरण के लिए कहा। सवाल पूछता है कि दोनों के बीच एक वैचारिक अंतर है । खैर, मुझे नहीं पता। निश्चित रूप से कई अंतर हैं; क्या उनमें से कोई भी "वैचारिक" मतभेदों के रूप में गिना जाता है? नेस्टेड फ़न के साथ मेरे उदाहरण में त्रुटि प्रसार में भी अंतर हैं; यदि स्थानीय स्थिति में कार्य बंद हो जाते हैं तो स्थानीय जीवनकाल में अंतर होता है, और इसी तरह। क्या ये "वैचारिक" अंतर हैं?
एरिक लिपर्ट

6
यह एक पुराना उत्तर है, लेकिन मेरा मानना ​​है कि आज के दिन को कम कर दिया जाएगा। यह सवाल का जवाब नहीं देता है, और न ही यह ओपी को एक ऐसे स्रोत की ओर इशारा करता है जहां से वह सीख सकता है।
डैनियल डुबोव्स्की

11
  1. पहली विधि भी संकलित नहीं है।

    चूँकि ' Program.TestAsync()' एक एसिंक्स विधि है जो 'रिटर्न Task' करता है, एक रिटर्न कीवर्ड का ऑब्जेक्ट एक्सप्रेशन के बाद नहीं होना चाहिए। क्या आपने ' Task<T>' लौटने का इरादा किया था ?

    इसे होना चाहिए

    async Task TestAsync()
    {
        await Task.Run(() => DoSomeWork());
    }
    
  2. इन दोनों के बीच प्रमुख वैचारिक अंतर है। पहला एक अतुल्यकालिक है, दूसरा वह नहीं है। Async प्रदर्शन पढ़ें: Async और Await की लागत को समझनाasync / के internals के बारे में थोड़ा और जानने के लिए await

  3. वे अलग कोड उत्पन्न करते हैं।

    .method private hidebysig 
        instance class [mscorlib]System.Threading.Tasks.Task TestAsync () cil managed 
    {
        .custom instance void [mscorlib]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [mscorlib]System.Type) = (
            01 00 25 53 4f 54 65 73 74 50 72 6f 6a 65 63 74
            2e 50 72 6f 67 72 61 6d 2b 3c 54 65 73 74 41 73
            79 6e 63 3e 64 5f 5f 31 00 00
        )
        .custom instance void [mscorlib]System.Diagnostics.DebuggerStepThroughAttribute::.ctor() = (
            01 00 00 00
        )
        // Method begins at RVA 0x216c
        // Code size 62 (0x3e)
        .maxstack 2
        .locals init (
            [0] valuetype SOTestProject.Program/'<TestAsync>d__1',
            [1] class [mscorlib]System.Threading.Tasks.Task,
            [2] valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder
        )
    
        IL_0000: ldloca.s 0
        IL_0002: ldarg.0
        IL_0003: stfld class SOTestProject.Program SOTestProject.Program/'<TestAsync>d__1'::'<>4__this'
        IL_0008: ldloca.s 0
        IL_000a: call valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::Create()
        IL_000f: stfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder SOTestProject.Program/'<TestAsync>d__1'::'<>t__builder'
        IL_0014: ldloca.s 0
        IL_0016: ldc.i4.m1
        IL_0017: stfld int32 SOTestProject.Program/'<TestAsync>d__1'::'<>1__state'
        IL_001c: ldloca.s 0
        IL_001e: ldfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder SOTestProject.Program/'<TestAsync>d__1'::'<>t__builder'
        IL_0023: stloc.2
        IL_0024: ldloca.s 2
        IL_0026: ldloca.s 0
        IL_0028: call instance void [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::Start<valuetype SOTestProject.Program/'<TestAsync>d__1'>(!!0&)
        IL_002d: ldloca.s 0
        IL_002f: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder SOTestProject.Program/'<TestAsync>d__1'::'<>t__builder'
        IL_0034: call instance class [mscorlib]System.Threading.Tasks.Task [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::get_Task()
        IL_0039: stloc.1
        IL_003a: br.s IL_003c
    
        IL_003c: ldloc.1
        IL_003d: ret
    } // end of method Program::TestAsync
    

    तथा

    .method private hidebysig 
        instance class [mscorlib]System.Threading.Tasks.Task TestAsync2 () cil managed 
    {
        // Method begins at RVA 0x21d8
        // Code size 23 (0x17)
        .maxstack 2
        .locals init (
            [0] class [mscorlib]System.Threading.Tasks.Task CS$1$0000
        )
    
        IL_0000: nop
        IL_0001: ldarg.0
        IL_0002: ldftn instance class [mscorlib]System.Threading.Tasks.Task SOTestProject.Program::'<TestAsync2>b__4'()
        IL_0008: newobj instance void class [mscorlib]System.Func`1<class [mscorlib]System.Threading.Tasks.Task>::.ctor(object, native int)
        IL_000d: call class [mscorlib]System.Threading.Tasks.Task [mscorlib]System.Threading.Tasks.Task::Run(class [mscorlib]System.Func`1<class [mscorlib]System.Threading.Tasks.Task>)
        IL_0012: stloc.0
        IL_0013: br.s IL_0015
    
        IL_0015: ldloc.0
        IL_0016: ret
    } // end of method Program::TestAsync2
    

@MarcinJuraszek, वास्तव में यह संकलन नहीं था। यह एक टाइपो था, मुझे यकीन है कि आप इसे सही समझ गए हैं। अन्यथा, एक महान जवाब, धन्यवाद! मुझे लगा कि पहले मामले में राज्य मशीन वर्ग बनाने से बचने के लिए सी # काफी स्मार्ट हो सकता है।
avo

9

दो उदाहरण हैं भिन्न होते हैं। जब कोई विधि asyncकीवर्ड के साथ चिह्नित की जाती है , तो कंपाइलर पर्दे के पीछे एक राज्य-मशीन बनाता है। एक बार प्रतीक्षित होने के बाद निरंतरता को फिर से शुरू करने के लिए यह जिम्मेदार है।

इसके विपरीत, जब कोई विधि आपके साथ चिह्नित नहीं की जाती है , asyncतो आपके पास awaitप्रतीक्षा करने की क्षमता खो जाती है । (अर्थात, विधि के भीतर; विधि अभी भी इसके कॉलर द्वारा प्रतीक्षा की जा सकती है।) हालांकि, asyncकीवर्ड से बचने से , आप अब राज्य-मशीन उत्पन्न नहीं कर रहे हैं, जो कि उचित ओवरहेड जोड़ सकते हैं (स्थानीय लोगों को खेतों में उठाकर) राज्य-मशीन की, जीसी को अतिरिक्त वस्तुएं)।

इस तरह के उदाहरणों में, यदि आप async-awaitसीधे प्रतीक्षा करने और वापस आने में सक्षम हैं, तो यह विधि की दक्षता में सुधार करने के लिए किया जाना चाहिए।

इस प्रश्न और इस उत्तर को देखें जो आपके प्रश्न और इस उत्तर के समान हैं।

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