Farklı sonuçlarla birden fazla Görev bekleniyor


237

3 görevim var:

private async Task<Cat> FeedCat() {}
private async Task<House> SellHouse() {}
private async Task<Tesla> BuyCar() {}

Hepsi kodum devam etmeden önce çalışması gerekiyor ve ben de her sonuçlara ihtiyacım var. Sonuçların hiçbirinin birbiriyle ortak bir yanı yok

3 görevi tamamlayıp sonuçları almak için nasıl ararım ve beklerim?


25
Herhangi bir sipariş gereksiniminiz var mı? Yani, kedi beslenene kadar evi satmak istemiyor musunuz?
Eric Lippert

Yanıtlar:


411

Kullandıktan sonra WhenAll, sonuçları aşağıdakilerle ayrı ayrı çıkarabilirsiniz await:

var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();

await Task.WhenAll(catTask, houseTask, carTask);

var cat = await catTask;
var house = await houseTask;
var car = await carTask;

Ayrıca kullanabilirsiniz Task.Result(bu noktaya kadar hepsi başarıyla tamamlandığını bildiğiniz için). Ancak, diğer senaryolarda sorunlara neden olabileceği için awaitaçıkça doğru olduğu için kullanmanızı öneririm Result.


83
Bunu WhenAllbundan tamamen kaldırabilirsiniz ; sizi bekleyen görevler tamamlanıncaya kadar 3 ödevin ötesine geçmemenizi sağlar.
Servy

134
Task.WhenAll()Görevin paralel modda çalıştırılmasına izin verir . @Servy'nin neden kaldırmayı önerdiğini anlayamıyorum. Onlar olmadan WhenAlltek tek yönetilecek
Sergey G.

87
@Sergey: Görevler hemen yürütülmeye başlar. Örneğin, catTaskgeri döndüğü zamana kadar zaten çalışıyor FeedCat. Yani her iki yaklaşım da işe yarayacaktır - tek soru, awaitonları birer birer mi yoksa hep birlikte mi yapmak istediğinizdir . Hata işleme biraz farklıdır - kullanırsanız Task.WhenAll, awaitbunlardan biri erken arızalansa bile hepsi olacaktır .
Stephen Cleary

23
@Sergey Calling'in WhenAllişlemlerin ne zaman yürütüldüğü veya nasıl yürütüldüğü üzerinde hiçbir etkisi yoktur. Bu sadece herhangi sahip ihtimalini sonuçları gözlenir nasıl etkileme. Bu özel durumda, tek fark, ilk iki yöntemden birindeki bir hatanın, istisnamın bu çağrı yığınında benim yöntemimde Stephen'ın daha erken atılmasına yol açmasıdır (her ne kadar aynı hata her zaman atılırsa) ).
Servise

37
@Sergey: Anahtar, zaman uyumsuz yöntemlerin her zaman "sıcak" (zaten başlatılmış) görevleri döndürmesidir.
Stephen Cleary

99

Sadece awaithepsini başladıktan sonra ayrı ayrı üç görevi.

var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();

var cat = await catTask;
var house = await houseTask;
var car = await carTask;

8
@Bargitta Hayır, bu yanlış. İşlerini paralel yapacaklar. Çalıştırın ve kendiniz görün.
16'da

5
İnsanlar aynı soruyu yıllar sonra sormaya devam ediyorlar ... Cevabın gövdesinde bir görevin " yaratmaya başladığını " tekrar vurgulamanın önemli olduğunu düşünüyorum : belki de yorumları okumaktan rahatsız

9
@StephenYork Task.WhenAllHerhangi bir gözlemlenebilir şekilde, programın davranışı hakkında tam anlamıyla değişiklik eklemek . Tamamen gereksiz bir yöntem çağrısıdır. İsterseniz, estetik bir seçim olarak ekleyebilirsiniz, ancak kodun ne yaptığını değiştirmez. Kodun yürütme süresi, bu yöntem çağrısı ile veya bu yöntem çağrısı olmadan aynı olacaktır (iyi, teknik olarak çağrı yapmak için gerçekten küçük bir ek yük olacaktır WhenAll, ancak bu ihmal edilebilir olmalıdır), bu sürümü sadece bu sürümden biraz daha uzun hale getirir .
17'de

4
@StephenYork Örneğiniz işlemleri iki nedenden dolayı sırayla çalıştırır. Eşzamansız yöntemleriniz aslında eşzamansız değil, eşzamanlıdır. Her zaman zaten tamamlanmış görevleri döndüren senkronize yöntemlere sahip olmanız, eşzamanlı olarak çalışmalarını engeller. Daha sonra, aslında üç eşzamansız yöntemi de başlatarak ve ardından üç görevi sırayla beklerken bu cevapta gösterilenleri yapmazsınız. Örneğiniz, bir önceki bitene kadar her yöntemi çağırmaz, dolayısıyla bu kodun aksine, bir öncekinin bitinceye kadar başlatılmasını açıkça engeller.
17'de Hizmet

4
@MarcvanNieuwenhuijzen Buradaki yorumlarda ve diğer cevaplarda tartışıldığı gibi bu açıkça doğru değil. Eklemek WhenAlltamamen estetik bir değişikliktir. Davranıştaki tek gözlemlenebilir fark, daha önce bir görevin başarısız olması durumunda daha sonraki görevlerin bitmesini bekleyip beklemediğinizdir. İfadenizin neden doğru olmadığına dair çok sayıda açıklamaya inanmıyorsanız, kodu kendiniz çalıştırabilir ve bunun doğru olmadığını görebilirsiniz.
Servy

37

C # 7 kullanıyorsanız, böyle kullanışlı bir sarmalama yöntemi kullanabilirsiniz ...

public static class TaskEx
{
    public static async Task<(T1, T2)> WhenAll<T1, T2>(Task<T1> task1, Task<T2> task2)
    {
        return (await task1, await task2);
    }
}

... farklı dönüş türlerine sahip birden çok görevi beklemek istediğinizde, bu gibi uygun sözdizimini etkinleştirmek için. Elbette, beklemek için farklı sayıda görev için birden fazla aşırı yükleme yapmanız gerekir.

var (someInt, someString) = await TaskEx.WhenAll(GetIntAsync(), GetStringAsync());

Ancak, bu örneği gerçek bir şeye dönüştürmek istiyorsanız, ValueTask ve zaten tamamlanmış görevler hakkındaki bazı optimizasyonlar için Marc Gravell'in cevabına bakın.


Tuples burada yer alan tek C # 7 özelliğidir. Bunlar kesinlikle son sürümde.
Joel Mueller

Ben tuples ve c # 7 biliyorum. Yani tuples dönen WhenAll yöntemini bulamıyorum. Hangi ad alanı / paket?
Yury Scherbakov

@YuryShcherbakov Task.WhenAll()bir demet döndürmüyor . Bunlardan biri, Resultverilen görev Task.WhenAll()tamamlandıktan sonra sağlanan görevlerin özelliklerinden oluşturuluyor .
Chris Charabaruk

2
.ResultDiğer kişilerin örneğinizi kopyalayarak kötü uygulamayı sürdürmekten kaçınmak için çağrıları Stephen'in muhakemesine göre değiştirmenizi öneririm .
julealgon

Acaba bu yöntem neden çerçevenin bu parçası değil? Çok faydalı görünüyor. Zamanları doldu mu ve tek bir dönüş türünde durmaları mı gerekiyor?
Ian Grainger

14

Verilen üç görevi - FeedCat(), SellHouse()veBuyCar() , iki ilginç durumlar vardır: ya onlar (nedense, belki de önbelleğe veya bir hata) tüm komple eşzamanlı veya onlar değil.

Diyelim ki sorudan:

Task<string> DoTheThings() {
    Task<Cat> x = FeedCat();
    Task<House> y = SellHouse();
    Task<Tesla> z = BuyCar();
    // what here?
}

Şimdi, basit bir yaklaşım şöyle olacaktır:

Task.WhenAll(x, y, z);

ancak ... sonuçların işlenmesi için uygun değildir; tipik olarak bunu yapmak awaitisteriz:

async Task<string> DoTheThings() {
    Task<Cat> x = FeedCat();
    Task<House> y = SellHouse();
    Task<Tesla> z = BuyCar();

    await Task.WhenAll(x, y, z);
    // presumably we want to do something with the results...
    return DoWhatever(x.Result, y.Result, z.Result);
}

ancak bu çok fazla yük oluşturur ve çeşitli dizileri ( params Task[]dizi dahil ) ve listeleri (dahili olarak) ayırır. Çalışıyor, ama harika IMO değil. Birçok yönden bir işlemi kullanmak daha kolaydırasync ve awaither biri sırayla:

async Task<string> DoTheThings() {
    Task<Cat> x = FeedCat();
    Task<House> y = SellHouse();
    Task<Tesla> z = BuyCar();

    // do something with the results...
    return DoWhatever(await x, await y, await z);
}

Kullanarak yukarıdaki bazı yorumlar, aksine awaityerine Task.WhenAllmarkaların fark görevleri (eş zamanlı, sıralı, vs) çalıştırmak nasıl. En yüksek düzeyde, / için iyi derleyici desteğinden Task.WhenAll önce gelir ve bu şeyler olmadığında yararlı oldu . Ayrıca, 3 gizli görev yerine rastgele bir dizi göreviniz olduğunda da kullanışlıdır.asyncawait

Ancak: devam için çok fazla derleyici gürültüsü üreten async/ awaitüreten bir sorun var . O görevler olması mümkündür olabilir aslında eşzamanlı tamamlamak, o zaman uyumsuz bir yedek kullanımıyla bir senkron yolunda inşa ederek bu optimize edebilirsiniz:

Task<string> DoTheThings() {
    Task<Cat> x = FeedCat();
    Task<House> y = SellHouse();
    Task<Tesla> z = BuyCar();

    if(x.Status == TaskStatus.RanToCompletion &&
       y.Status == TaskStatus.RanToCompletion &&
       z.Status == TaskStatus.RanToCompletion)
        return Task.FromResult(
          DoWhatever(a.Result, b.Result, c.Result));
       // we can safely access .Result, as they are known
       // to be ran-to-completion

    return Awaited(x, y, z);
}

async Task Awaited(Task<Cat> a, Task<House> b, Task<Tesla> c) {
    return DoWhatever(await x, await y, await z);
}

Bu "eşzamansız geri dönüş ile eşitleme" yaklaşımı, özellikle eşzamanlı tamamlamaların nispeten sık olduğu yüksek performanslı kodda giderek yaygınlaşmaktadır. Tamamlamanın her zaman gerçekten eşzamansız olması durumunda hiç yardımcı olmayacağını unutmayın.

Burada geçerli olan ek şeyler:

  1. son C # ile, ortak bir model, asyncgeri dönüş yöntemi genellikle yerel bir işlev olarak uygulanır:

    Task<string> DoTheThings() {
        async Task<string> Awaited(Task<Cat> a, Task<House> b, Task<Tesla> c) {
            return DoWhatever(await a, await b, await c);
        }
        Task<Cat> x = FeedCat();
        Task<House> y = SellHouse();
        Task<Tesla> z = BuyCar();
    
        if(x.Status == TaskStatus.RanToCompletion &&
           y.Status == TaskStatus.RanToCompletion &&
           z.Status == TaskStatus.RanToCompletion)
            return Task.FromResult(
              DoWhatever(a.Result, b.Result, c.Result));
           // we can safely access .Result, as they are known
           // to be ran-to-completion
    
        return Awaited(x, y, z);
    }
  2. tercih ValueTask<T>etmek Task<T>çok farklı dönüş değerleri ile şimdiye tamamen eşzamanlı şeylerin iyi bir şans varsa:

    ValueTask<string> DoTheThings() {
        async ValueTask<string> Awaited(ValueTask<Cat> a, Task<House> b, Task<Tesla> c) {
            return DoWhatever(await a, await b, await c);
        }
        ValueTask<Cat> x = FeedCat();
        ValueTask<House> y = SellHouse();
        ValueTask<Tesla> z = BuyCar();
    
        if(x.IsCompletedSuccessfully &&
           y.IsCompletedSuccessfully &&
           z.IsCompletedSuccessfully)
            return new ValueTask<string>(
              DoWhatever(a.Result, b.Result, c.Result));
           // we can safely access .Result, as they are known
           // to be ran-to-completion
    
        return Awaited(x, y, z);
    }
  3. Mümkünse, tercih IsCompletedSuccessfullyetmek Status == TaskStatus.RanToCompletion; bu artık .NET Core'da Taskve her yerde varValueTask<T>


"Burada çeşitli cevapların aksine, Görev yerine bekliyor.WhenAll görevlerin nasıl çalıştığında (aynı anda, sırayla, vb.) Hiçbir fark yaratmaz" Bunu söyleyen bir cevap görmüyorum. Zaten yaptıklarını söyleyerek onlara yorum yapmıştım. Bunu söyleyen birçok cevap hakkında çok sayıda yorum var, ancak cevap yok. Hangisine atıfta bulunuyorsunuz? Ayrıca cevabınızın görevlerin sonucunu ele almadığını (veya sonuçların farklı türlerde olduğu gerçeğiyle ilgilenmediğini) unutmayın. Bunları, Taskhepsi bitmeden sonuçları kullanmadan döndüren bir yöntemde oluşturdunuz .
Servy

@ Haklısın, bu yorumlardı; Sonuçları kullanarak göstermek için bir tweak ekleyeceğim
Marc Gravell

@Servy tweak eklendi
Marc Gravell

Ayrıca eşzamanlı görevleri teslim etmenin erken dönemini gerçekleştirecekseniz, başarıyla tamamlananlar yerine eşzamanlı olarak iptal edilen veya hatalı olan görevleri de halledebilirsiniz. Programınızın ihtiyaç duyduğu bir optimizasyon olduğuna karar verdiyseniz (nadiren olacak, ancak olacak), o zaman da sonuna kadar gidebilirsiniz.
Servy

@Servy bu karmaşık bir konudur - iki senaryodan farklı istisna semantikleri alırsınız - bir istisnayı tetiklemeyi beklemek, istisnayı tetiklemek için sonuçtan farklı davranır. Bu noktada IMO, awaitistisnaların nadir fakat anlamlı olduğu varsayımıyla "daha iyi" istisna anlambilimi elde etmeliyiz
Marc Gravell

12

Onları görevlerde saklayabilir, sonra hepsini bekleyebilirsiniz:

var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();

await Task.WhenAll(catTask, houseTask, carTask);

Cat cat = await catTask;
House house = await houseTask;
Car car = await carTask;

gelmez var catTask = FeedCat()işlevi gerçekleştirmek FeedCat()ve içine sonucunu saklamak catTaskyapım await Task.WhenAll()yöntemi zaten idam beri yararsız bir parçası tür ??
Kraang Prime

1
@sanuel görevi <t> döndürürlerse, o zaman hayır ... zaman uyumsuzluğu başlatırlar, ama beklemezler
Reed Copsey

Bunun doğru olduğunu düşünmüyorum, lütfen @ StephenCleary'nin cevabı altındaki tartışmalara bakın ... ayrıca Servy'nin cevabına da bakın.
Rosdi Kasim

1
.ConfigrtueAwait (false) eklemem gerekiyorsa. Bunu sadece Görev'e ekleyebilir miyim?
AstroSharp

@AstroSharp genel olarak, hepsine eklemek iyi bir fikirdir (ilk tamamlanırsa, etkili bir şekilde göz ardı edilir), ancak bu durumda, ilkini yapmak muhtemelen daha iyi olur - daha fazla zaman uyumsuz olmadıkça şeyler daha sonra oluyor.
Reed Copsey

6

Tüm hataları günlüğe kaydetmeye çalışıyorsanız, Task.WhenAll satırını kodunuzda tuttuğunuzdan emin olun, çok sayıda yorum onu ​​kaldırabileceğinizi ve bireysel görevleri bekleyebileceğinizi gösterir. Görev.Ne zaman hata işleme için gerçekten önemlidir. Bu satır olmadan, kodunuzu gözlemlenmeyen istisnalar için açık bırakabilirsiniz.

var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();

await Task.WhenAll(catTask, houseTask, carTask);

var cat = await catTask;
var house = await houseTask;
var car = await carTask;

FeedCat'in aşağıdaki kodda istisna oluşturduğunu düşünün:

var catTask = FeedCat();
var houseTask = SellHouse();
var carTask = BuyCar();

var cat = await catTask;
var house = await houseTask;
var car = await carTask;

Bu durumda asla houseTask veya carTask'ta beklemeyeceksiniz. Burada 3 olası senaryo vardır:

  1. FeedCat başarısız olduğunda SellHouse zaten başarıyla tamamlandı. Bu durumda iyisin.

  2. SellHouse tam değildir ve bir noktada istisna dışında başarısız olur. İstisna gözlenmez ve sonlandırıcı iplikte yeniden görüntülenir.

  3. SellHouse tam değil ve içinde bekliyor. Kodunuzun ASP.NET'te çalıştırılması durumunda, bazı beklemeler içinde tamamlanır tamamlanmaz başarısız olur. Bunun nedeni, FeedCat başarısız olur olmaz temelde ateşle & unut araması ve senkronizasyon bağlamının kaybolmasıdır.

Dava (3) için alacağınız hata:

System.AggregateException: A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread. ---> System.NullReferenceException: Object reference not set to an instance of an object.
   at System.Web.ThreadContext.AssociateWithCurrentThread(Boolean setImpersonationContext)
   at System.Web.HttpApplication.OnThreadEnterPrivate(Boolean setImpersonationContext)
   at System.Web.HttpApplication.System.Web.Util.ISyncContext.Enter()
   at System.Web.Util.SynchronizationHelper.SafeWrapCallback(Action action)
   at System.Threading.Tasks.Task.Execute()
   --- End of inner exception stack trace ---
---> (Inner Exception #0) System.NullReferenceException: Object reference not set to an instance of an object.
   at System.Web.ThreadContext.AssociateWithCurrentThread(Boolean setImpersonationContext)
   at System.Web.HttpApplication.OnThreadEnterPrivate(Boolean setImpersonationContext)
   at System.Web.HttpApplication.System.Web.Util.ISyncContext.Enter()
   at System.Web.Util.SynchronizationHelper.SafeWrapCallback(Action action)
   at System.Threading.Tasks.Task.Execute()<---

Durum (2) için benzer bir hata alırsınız, ancak orijinal istisna yığını izlemesiyle.

.NET 4.0 ve sonraki sürümler için TaskScheduler.UnobservedTaskException kullanarak gözlemlenmeyen özel durumları yakalayabilirsiniz. .NET 4.5 ve sonraki sürümlerde gözlemlenmeyen özel durumlar varsayılan olarak yutulur. NET 4.0 için gözlenmeyen özel durum işleminizin çökmesine neden olur.

Daha fazla ayrıntı burada: .NET 4.5'te Görev Özel Durumu İşleme


2

Task.WhenAllBelirtildiği gibi veya Task.WaitAlliş parçacığının beklemesini isteyip istemediğinize bağlı olarak kullanabilirsiniz . Her ikisinin açıklaması için bağlantıya bir göz atın.

WaitAll ve WhenAll


2

Kullanın Task.WhenAllve ardından sonuçları bekleyin:

var tCat = FeedCat();
var tHouse = SellHouse();
var tCar = BuyCar();
await Task.WhenAll(tCat, tHouse, tCar);
Cat cat = await tCat;
House house = await tHouse;
Tesla car = await tCar; 
//as they have all definitely finished, you could also use Task.Value.

mm ... Görev Değeri değil (belki 2013'te mevcuttu?), daha ziyade tCat.Result, tHouse.Result veya tCar.Result
Stephen York

1

İleri Uyarı

Bu ve benzeri konuları ziyaret edenlere, async + await + görev araç setini kullanarak EntityFramework'i paralelleştirmenin bir yolunu arayanlara hızlı bir başlangıç : Burada gösterilen desen , ancak EF'in özel kar tanesi söz konusu olduğunda, dahil olan her * Async () çağrısının içinde ayrı (yeni) db-context-örneği kullanmadığınız sürece ve buna kadar paralel yürütme elde edin.

Bu tür bir şey, aynı ef-db-bağlam örneğinde paralel olarak birden çok sorguyu çalıştırmayı yasaklayan ef-db-bağlamlarının doğal tasarım sınırlamaları nedeniyle gereklidir.


Daha önce verilen cevaplardan yararlanarak, görevlerden biri veya daha fazlasının istisna oluşturması durumunda bile tüm değerleri topladığınızdan emin olmanın yolu budur:

  public async Task<string> Foobar() {
    async Task<string> Awaited(Task<Cat> a, Task<House> b, Task<Tesla> c) {
        return DoSomething(await a, await b, await c);
    }

    using (var carTask = BuyCarAsync())
    using (var catTask = FeedCatAsync())
    using (var houseTask = SellHouseAsync())
    {
        if (carTask.Status == TaskStatus.RanToCompletion //triple
            && catTask.Status == TaskStatus.RanToCompletion //cache
            && houseTask.Status == TaskStatus.RanToCompletion) { //hits
            return Task.FromResult(DoSomething(catTask.Result, carTask.Result, houseTask.Result)); //fast-track
        }

        cat = await catTask;
        car = await carTask;
        house = await houseTask;
        //or Task.AwaitAll(carTask, catTask, houseTask);
        //or await Task.WhenAll(carTask, catTask, houseTask);
        //it depends on how you like exception handling better

        return Awaited(catTask, carTask, houseTask);
   }
 }

Aşağı yukarı aynı performans özelliklerine sahip alternatif bir uygulama şunlar olabilir:

 public async Task<string> Foobar() {
    using (var carTask = BuyCarAsync())
    using (var catTask = FeedCatAsync())
    using (var houseTask = SellHouseAsync())
    {
        cat = catTask.Status == TaskStatus.RanToCompletion ? catTask.Result : (await catTask);
        car = carTask.Status == TaskStatus.RanToCompletion ? carTask.Result : (await carTask);
        house = houseTask.Status == TaskStatus.RanToCompletion ? houseTask.Result : (await houseTask);

        return DoSomething(cat, car, house);
     }
 }

-1
var dn = await Task.WhenAll<dynamic>(FeedCat(),SellHouse(),BuyCar());

Cat'e erişmek istiyorsanız, bunu yapın:

var ct = (Cat)dn[0];

Bunu yapmak çok basit ve kullanımı çok yararlı, karmaşık bir çözümden sonra gitmeye gerek yok.


1
Bununla ilgili tek bir sorun var: dynamicşeytan. Bu zor COM birlikte çalışma ve benzeri için ve kesinlikle gerekli olmadığı hiçbir durumda kullanılmamalıdır. Özellikle performansı önemsiyorsanız. Veya güvenliği yazın. Ya da yeniden düzenleme. Veya hata ayıklama.
Joel Mueller
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.