C # 'da "dönüş bekliyor" amacı nedir?


251

Böyle yazma yönteminin olduğu herhangi bir senaryo var mı :

public async Task<SomeResult> DoSomethingAsync()
{
    // Some synchronous code might or might not be here... //
    return await DoAnotherThingAsync();
}

bunun yerine:

public Task<SomeResult> DoSomethingAsync()
{
    // Some synchronous code might or might not be here... //
    return DoAnotherThingAsync();
}

anlamlı olur mu?

Neden iç çağrışımdan return awaitdoğrudan geri dönebiliyorsanız yapı kullanıyorsunuz ?Task<T>DoAnotherThingAsync()

return awaitBirçok yerde kod görüyorum , sanırım bir şey kaçırmış olabilirim. Ama anladığım kadarıyla, bu durumda async / await anahtar sözcüklerini kullanmamak ve doğrudan Görevi döndürmek işlevsel olarak eşdeğer olacaktır. Neden ek awaitkatman ek yükü eklemelisiniz ?


2
Bunu görmenizin tek nedeni, insanların taklit yoluyla öğrenmeleri ve genellikle (ihtiyaç duymadıkları takdirde) bulabilecekleri en basit çözümü kullanmalarıdır. Böylece insanlar bu kodu görür, bu kodu kullanır, işe yaradığını görürler ve bundan böyle onlar için bunu yapmanın doğru yolu ... Bu durumda
beklemenin bir

7
En az bir önemli fark vardır: istisna yayılımı .
noseratio

1
Ben de anlamıyorum, bu kavramın tamamını anlayamıyorum, hiçbir anlam ifade etmiyor. Bir yöntemin bir dönüş türü varsa, öğrendiğimden, bir dönüş anahtar kelimesi olmalı, C # dil kuralları değil mi?
monstro

@monstro OP sorusunun dönüş ifadesi var mı?
David Klempfner

Yanıtlar:


189

Sinsi bir durum vardır return, normal yöntem ve return awaitde asyncfarklı yöntem davranır: ile birleştirildiğinde using(ya da daha genel olarak, herhangi bir return awaita tryblok).

Bir yöntemin şu iki sürümünü düşünün:

Task<SomeResult> DoSomethingAsync()
{
    using (var foo = new Foo())
    {
        return foo.DoAnotherThingAsync();
    }
}

async Task<SomeResult> DoSomethingAsync()
{
    using (var foo = new Foo())
    {
        return await foo.DoAnotherThingAsync();
    }
}

İlk yöntem olacak nesne kısa sürede aslında tamamlanmadan önce uzun muhtemeldir metot bize. Bu, ilk sürümün muhtemelen hatalı olduğu (çünkü çok yakında atıldığı) anlamına gelirken, ikinci sürüm iyi çalışır.Dispose()FooDoAnotherThingAsync()Foo


4
foo.DoAnotherThingAsync().ContinueWith(_ => foo.Dispose());
Tamlık

7
@ghord Bu işe yaramaz, Dispose()geri döner void. Gibi bir şeye ihtiyacınız var return foo.DoAnotherThingAsync().ContinueWith(t -> { foo.Dispose(); return t.Result; });. Ama ikinci seçeneği kullanabileceğiniz zaman bunu neden yaptınız bilmiyorum.
svick

1
@svick Haklısın, çizgileri boyunca daha fazla olmalı { var task = DoAnotherThingAsync(); task.ContinueWith(_ => foo.Dispose()); return task; }. Kullanım durumu oldukça basittir: .NET 4.0'da (çoğu gibi), yine de 4.5 uygulamalardan güzelce çalışacak şekilde bu şekilde zaman uyumsuz kod yazabilirsiniz.
ghord

2
@ghord .Net 4.0 kullanıyorsanız ve eşzamansız kod yazmak istiyorsanız, büyük olasılıkla Microsoft.Bcl.Async kullanmalısınız . Ve kodunuz Foosadece Tasksevmediğim iade tamamlandıktan sonra atıyor , çünkü gereksiz yere eşzamanlılık getiriyor.
svick

1
@svick Kodunuz görev de bitene kadar bekler. Ayrıca, Microsoft.Bcl.Async, KB2468871'e bağımlılık nedeniyle uygun değil ve .NET 4.0 zaman uyumsuz kod tabanını uygun 4.5 zaman uyumsuz kodla kullanırken çakışıyor.
ghord

93

İhtiyacınız yoksa async(yani, Taskdoğrudan iade edebilirsiniz ), kullanmayın async.

return awaitYararlı olan iki durum vardır , örneğin iki eşzamansız işleminiz var:

var intermediate = await FirstAsync();
return await SecondAwait(intermediate);

asyncPerformans hakkında daha fazla bilgi için , Stephen Toub'un konu hakkındaki MSDN makalesine ve videosuna bakın .

Güncelleme: Çok daha ayrıntılı bir blog yazısı yazdım .


13
awaitİkinci durumda bunun neden yararlı olduğuna dair bir açıklama ekleyebilir misiniz ? Neden yapmıyorsunuz return SecondAwait(intermediate);?
Matt Smith

2
Matt ile aynı sorum var return SecondAwait(intermediate);, bu durumda da hedefe ulaşamaz mıydım? Sanırım return awaitburada da gereksiz ...
TX_

23
@MattSmith Bu derlemez. awaitİlk satırda kullanmak istiyorsanız , ikinci satırda da kullanmanız gerekir.
svick

2
@cateyes “Onlarla paralellik yaparak ek yükün” ne anlama geldiğinden emin değilim, ancak asyncsürüm senkron sürümünüzden daha az kaynak (iş parçacığı) kullanacak .
svick

4
@TomLint Gerçekten derlenmiyor . Dönüş türünün SecondAwait"string" olduğu varsayılarak hata mesajı şöyledir: "CS4016: Bu bir zaman uyumsuz yöntem olduğundan, dönüş ifadesi 'Task <string>' yerine 'string' türünde olmalıdır".
svick

23

Bunu yapmak istemenin tek nedeni await, önceki kodda başka bir tane varsa veya sonucu bir şekilde döndürmeden önce manipüle ediyorsanız. Bunun olabileceği başka bir yol, try/catchistisnaların ele alınış biçimini değiştiren bir yöntemdir . Bunlardan herhangi birini yapmıyorsanız, o zaman haklısınız, yöntemi yapma yükünü eklemek için hiçbir neden yoktur async.


4
Stephen'ın cevabında olduğu gibi, önceki kodda başka bir bekleyiş olsa bile neden return awaitgerekli olacağını anlamıyorum (sadece çocuk çağırma görevini geri vermek yerine) . Lütfen açıklama yapabilir misiniz?
TX_

10
@TX_ Kaldırmak asyncisterseniz, ilk görevi nasıl beklersiniz ? Yöntemi, herhangi bir beklemeyi asynckullanmak istiyormuş gibi işaretlemeniz gerekir . Yöntem olarak işaretlenmişse ve daha önce bir kodunuz varsa , uygun türde olması için ikinci zaman uyumsuz işlemine ihtiyacınız vardır . Yeni çıkardıysanız , dönüş değeri uygun türde olmayacağı için derlenmez. Yöntem olduğu için sonuç her zaman bir göreve sarılır. asyncawaitawaitawaitasync
Servy

11
@ Noseratio İkisini deneyin. İlk derler. İkincisi değil. Hata mesajı size sorunu bildirir. Uygun türü iade etmeyeceksiniz. Bir asyncyöntemde bir görev döndürmediğinizde, görevin sarılı olarak döndürüleceği görevin sonucunu döndürürsünüz.
Servy

3
@Servy, elbette - haklısın. İkinci durumda Task<Type>, (derleyicinin dönüştüğü ) asyncgeri dönmek için emir verirken, açıkça dönecekti . TypeTask<Type>
noseratio

3
@Itsik Tabii asyncdevamları kablolamak için sadece sözdizimsel şeker. Sen değil ihtiyaç async şey yapmak için, ancak herhangi bir önemsiz olmayan asenkron operasyon hakkında sadece yaparken bu kadar dramatik çalışmak daha kolay. Örneğin, sağladığınız kod hataları istediğiniz gibi yaymaz ve daha karmaşık durumlarda düzgün bir şekilde yapmak çok daha zorlaşmaya başlar. Asla ihtiyacınız async olmasa da, tarif ettiğim durumlar, onu kullanmak için değer kattığı yerlerdir.
Servy

17

Sonucu beklemeniz gerekebilecek başka bir durum şudur:

async Task<IFoo> GetIFooAsync()
{
    return await GetFooAsync();
}

async Task<Foo> GetFooAsync()
{
    var foo = await CreateFooAsync();
    await foo.InitializeAsync();
    return foo;
}

Bu durumda, GetIFooAsync()sonucunu beklemek gerekir GetFooAsynctipi nedeniyle Tiki yöntem arasındaki farklı ve Task<Foo>doğrudan atanabilir değil Task<IFoo>. Eğer sonuç bekliyor Ama eğer, sadece olur Foohangi olduğu doğrudan atanabilir IFoo. Daha sonra async yöntemi sadece sonucu içeride Task<IFoo>ve dışarıda yeniden paketler .


1
Katılıyorum, bu gerçekten sinir bozucu - inanıyorum ki altta yatan neden Task<>değişmez.
StuartLC

7

Aksi takdirde basit "thunk" yöntemi async yapmak bellekte bir async durum makinesi yaratırken async olmayan bir yöntem oluşturmaz. Bu daha çok (daha doğrudur) daha verimli (bu doğru) çünkü millet async olmayan sürümünü kullanmak için işaret ederken, aynı zamanda asmak durumunda, bu yöntemin "dönüş / devam yığını" dahil olduğunu gösteren hiçbir kanıt var anlamına gelir bu da bazen asmayı anlamayı zorlaştırır.

Yani evet, perf kritik olmadığında (ve genellikle değil) tüm bu thunk yöntemlerine async atacağım, böylece daha sonra asılmamı teşhis etmeme yardımcı olacak async durum makinesine sahibim ve ayrıca thunk yöntemleri zamanla geliştiğinde, atmak yerine hatalı görevleri geri getireceklerinden emin olacaklar.


6

Geri dönüşü kullanmayacaksanız, hata ayıklama sırasında veya istisnalar dışında günlüklerde yazdırıldığında yığın izinizi bozabilirsiniz.

Görevi döndürdüğünüzde, yöntem amacını yerine getirdi ve çağrı yığınının dışında. Kullandığınızda return await, çağrı yığınında bırakıyorsunuz.

Örneğin:

Beklerken kullanırken çağrı yığını: Görevi B'den beklerken B => B'den görevi C'den beklerken

Beklemede değilken çağrı yığını : Görevin B'den döndüğü C'den bekleyen bir beklemedir.


2

Bu da beni karıştırıyor ve önceki cevapların asıl sorunuzun göz ardı ettiğini hissediyorum:

Görevi doğrudan iç DoAnotherThingAsync () çağrısından döndürebildiğinizde neden return await yapısını kullanıyorsunuz?

Eh bazen aslında bir istiyorum Task<SomeType>, ancak çoğu zaman aslında bir örneğini istiyoruz SomeType, görevden sonucudur.

Kodunuzdan:

async Task<SomeResult> DoSomethingAsync()
{
    using (var foo = new Foo())
    {
        return await foo.DoAnotherThingAsync();
    }
}

Sözdizimine aşina olmayan bir kişi (örneğin, ben) bu yöntemin a döndürmesi gerektiğini düşünebilir Task<SomeResult>, ancak işaretlendiğinden async, gerçek dönüş türünün olduğu anlamına gelir SomeResult. Sadece kullanırsanız return foo.DoAnotherThingAsync(), derlenmeyecek bir Görev iade edersiniz. Doğru yol, görevin sonucunu döndürmektir return await.


1
msgstr "gerçek dönüş türü". Eh? async / await dönüş türlerini değiştirmiyor. Örnekte var task = DoSomethingAsync();Sana bir görev değil, verecektiT
Ayakkabı

2
@Shoe async/awaitşeyi iyi anladığımdan emin değilim . Benim anlayış için, Task task = DoSomethingAsync()iken Something something = await DoSomethingAsync()hem iş hem de. Birincisi size uygun görevi verirken, ikincisi, awaitanahtar kelime nedeniyle , görev tamamlandıktan sonra size sonuç verir . Mesela yapabilirdim Task task = DoSomethingAsync(); Something something = await task;.
17:51, heltonbiker
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.