“Await Task.Run (); dönüş;" ve "Task.Run () döndür"?


90

Aşağıdaki iki kod parçası arasında herhangi bir kavramsal fark var mı:

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

ve

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

Üretilen kod da farklı mı?

DÜZENLEME: Karışıklığı önlemek Task.Runiçin benzer bir durum

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

ve

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

GEÇ GÜNCELLEME: Kabul edilen cevaba ek olarak, nasıl LocalCallContextişlendiği konusunda da bir fark vardır : CallContext.LogicalGetData, eşzamansız olmadığında bile geri yüklenir. Neden?


1
Evet farklıdır. Ve çok farklı. Aksi kullanmanın hiçbir noktası olacağını await/ asynchepsi :) en
MarcinJuraszek

1
Sanırım burada iki soru var. 1. Yöntemin gerçek uygulaması arayan için önemli mi? 2. İki yöntemin derlenmiş sunumları farklı mı?
DavidRR

Yanıtlar:


81

Önemli bir fark, istisna yayılımıdır. Bir içinde atılan bir istisna, async Taskyöntem, geri depolanır Tasknesne ve görev ile gözlenen alana kadar atıl kalır await task, task.Wait(), task.Resultya da task.GetAwaiter().GetResult(). Yöntemin eşzamanlı kısmından atılsa bile bu şekilde yayılır async.

Şu kodu göz önünde bulundurun, burada OneTestAsyncve AnotherTestAsyncoldukça farklı davranır:

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);
    }
}

Ben ararsam DoTestAsync(OneTestAsync, -2), aşağıdaki çıktıyı üretir:

Devam etmek için enter tuşuna basın
Hata: Bir veya daha fazla hata oluştu. Await Task.Delay
Hata: 2.

Not, Entergörmek için basmam gerekti.

Şimdi ararsam DoTestAsync(AnotherTestAsync, -2) , içindeki kod iş akışı DoTestAsyncoldukça farklıdır ve çıktı da öyle. Bu sefer basmam istenmedi Enter:

Hata: Değerin -1 (sonsuz bir zaman aşımı anlamına gelir), 0 veya pozitif bir tam sayı olması gerekir.
Parametre adı: milisaniyeDelayError: 1.

Her iki durumda da Task.Delay(-2), parametrelerini doğrularken başlangıca atar. Bu uydurma bir senaryo olabilir, ancak teorideTask.Delay(1000) , örneğin temel sistem zamanlayıcı API'si başarısız olduğunda da fırlatabilir.

Bir yan not olarak, hata yayılma mantığı async voidyöntemler için henüz farklıdır ( yöntemlerin aksine async Task). Bir async voidyöntemin içinde ortaya çıkan bir istisna SynchronizationContext.Post, mevcut iş parçacığının bir tane varsa ( SynchronizationContext.Current != null).ThreadPool.QueueUserWorkItem ). Arayan kişinin bu istisnayı aynı yığın çerçevesinde işleme şansı yoktur.

Burada ve burada TPL istisna işleme davranışı hakkında daha fazla ayrıntı yayınladım .


S : asyncEşzamansız olmayan yöntemler için yöntemlerin istisna yayılma davranışını taklit etmek Task, böylece ikincisi aynı yığın çerçevesine atılmaması mümkün müdür ?

C : Gerçekten gerekliyse, evet, bunun için bir numara var:

// 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;
}

Bununla birlikte, belirli koşullar altında (yığında çok derin olduğunda olduğu gibi) RunSynchronouslyyine de eşzamansız olarak çalıştırılabileceğini unutmayın.


Bir başka önemli fark olduğunu / versiyon ölü kilit varsayılan olmayan bir senkronizasyon bağlamına daha yatkın olduğunu . Örneğin, aşağıdakiler bir WinForms veya WPF uygulamasında kilitlenecektir:asyncawait

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

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

Eşzamansız olmayan bir sürüme değiştirin ve kilitlenmeyecektir:

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

Kilitlenmenin doğası Stephen Cleary tarafından blogunda iyi açıklanmıştır .


2
İlk örnekteki kilitlenmenin, bekleme satırına .ConfigureAwait (false) eklenerek önlenebileceğine inanıyorum, çünkü bu yalnızca yöntem aynı yürütme bağlamına dönmeye çalıştığı için olur. Öyleyse geriye kalan tek fark istisnalar.
nispeten_random

2
Cevap arasındaki fark ile ilgili olmasına rağmen @relatively_random, yorumunuz, doğru return Task.Run()ve await Task.Run(); returnyerineawait Task.Run().ConfigureAwait(false); return
noseratio

Enter tuşuna bastıktan sonra programın kapandığını görürseniz, F5 yerine ctrl + F5 yaptığınızdan emin olun.
David Klempfner

53

Arasındaki fark nedir

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

ve

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

?

Bu soru kafam karıştı. Sorunuza başka bir soruyla cevap vererek açıklığa kavuşturmaya çalışayım. Arasındaki fark nedir?

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

ve

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

?

Benim iki şeyim arasındaki fark ne olursa olsun, iki şeyiniz arasında aynı fark var.


23
Elbette! Gözlerimi açtın :) İlk durumda, anlamsal olarak yakın bir sarmalayıcı görevi oluşturuyorum Task.Delay(1000).ContinueWith(() = {}). İkincisinde, sadece Task.Delay(1000). Fark biraz ince ama önemli.
avo

3
Farkı biraz açıklayabilir misin? aslında ben yapmam .. Teşekkür ederim
zheng yu

4
Eşzamanlı bağlamlarda ince bir fark olduğu ve istisna yayılmasının olduğu göz önüne alındığında, async / await ve işlev sarmalayıcılar arasındaki farkın aynı olmadığını söyleyebilirim.
Cameron MacFarland

1
@CameronMacFarland: Bu yüzden açıklama istedim. Soru , ikisi arasında kavramsal bir fark olup olmadığını sorar . Ben bilmiyorum. Kesinlikle birçok farklılık var; bunlardan herhangi biri "kavramsal" farklılıklar olarak sayılıyor mu? İç içe geçmiş işlevler örneğimde, hata yayılmasında da farklılıklar vardır; işlevler yerel eyalet üzerinden kapatılırsa, yerel yaşam sürelerinde farklılıklar olur ve bu böyle devam eder. Bunlar "kavramsal" farklılıklar mı?
Eric Lippert

8
Bu eski bir cevap, ama bugün verildiğine inanıyorum, olumsuz oy verilecek. Soruya cevap vermiyor, ne de OP'yi öğrenebileceği bir kaynağa işaret ediyor.
Daniel Dubovski

11
  1. İlk yöntem derleme bile yapmıyor.

    ' Program.TestAsync()', ' ' Döndüren bir eşzamansız yöntem olduğundan Task, bir dönüş anahtar sözcüğünden sonra bir nesne ifadesi gelmemelidir. Dönmeyi düşündün mü ' Task<T>'?

    Olmak zorunda

    async Task TestAsync()
    {
        await Task.Run(() => DoSomeWork());
    }
    
  2. Bu ikisi arasında büyük bir kavramsal fark var. Birincisi asenkron, ikincisi ise değil. Okuma zaman uyumsuz Performansı: zaman uyumsuz ve bekliyor Maliyetleri anlama donanımları hakkında biraz daha almak için async/ ' await.

  3. Farklı kod üretirler.

    .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
    

    ve

    .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, gerçekten derlemedi. Bu bir yazım hatasıydı, eminim doğru anladınız. Aksi takdirde, harika bir cevap, teşekkürler! İlk durumda bir durum makine sınıfı oluşturmaktan kaçınmak için C # 'nın yeterince akıllı olabileceğini düşündüm.
avo

9

İki örnek yok farklıdır. Bir yöntem ile işaretlendiğindeasync anahtar kelimeyle , derleyici perde arkasında bir durum makinesi oluşturur. Beklenebilir bir beklendikten sonra devam ettirmeye devam etmekten sorumlu olan budur.

Buna karşılık, zaman bir yöntemdir edilir değil işaretli asyncsen yeteneğini kaybediyor awaitawaitables. (Yani, yöntemin kendi içinde; yöntem arayan tarafından hala beklenebilir.) Bununla birlikte, asyncanahtar kelimeden kaçınarak, artık bir miktar ek yük ekleyebilen durum makinesini oluşturmuyorsunuz (yerelleri alanlara kaldırma durum makinesinin, GC'ye ek nesneler).

Bunun gibi örneklerde, async-awaitbeklenebilir bir durumdan doğrudan kaçınıp geri dönebiliyorsanız, yöntemin etkinliğini artırmak için yapılmalıdır.

Bkz bu soruyu ve bu cevabı sorunuzun ve bu cevaba çok benzer.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.