Birden fazla zaman uyumsuz görev yürütme ve hepsinin tamamlanmasını bekliyor


265

Bir konsol uygulamasında birden çok zaman uyumsuz görevi çalıştırmam ve daha fazla işlem yapmadan önce bunların tamamlanmasını beklemem gerekiyor.

Orada birçok makale var, ama okudukça kafam karışıyor gibi görünüyor. Görev kütüphanesinin temel prensiplerini okudum ve anladım, ancak bir yerlerde bir bağlantıyı açıkça kaçırıyorum.

Görevleri zincirlemenin mümkün olduğunu anlıyorum, böylece başka bir tamamlandıktan sonra başlıyorlar (bu, okuduğum tüm makaleler için neredeyse senaryo), ancak tüm Görevlerimin aynı anda çalışmasını istiyorum ve bir kez bilmek istiyorum hepsi tamamlandı.

Böyle bir senaryo için en basit uygulama nedir?

Yanıtlar:


440

Her iki cevap da beklenenden bahsetmedi Task.WhenAll:

var task1 = DoWorkAsync();
var task2 = DoMoreWorkAsync();

await Task.WhenAll(task1, task2);

Arasındaki temel fark, Task.WaitAllve Task.WhenAllönceki olacak bloğu (kullanarak benzer olduğunu Waitikinci ise tek bir görev) ve tüm görevler bitene kadar arayana kontrol geri vererek, beklenen edilebilir olmayacaktır.

Dahası, istisna işleme farklıdır:

Task.WaitAll:

Görev örneklerinden en az biri iptal edildi -veya- Görev örneklerinden en az birinin yürütülmesi sırasında bir istisna atıldı. Bir görev iptal edilirse, AggregateException kendi InnerExceptions koleksiyonunda bir OperationCanceledException içerir.

Task.WhenAll:

Sağlanan görevlerden herhangi biri hatalı bir durumda tamamlanırsa, döndürülen görev de Hatalı bir durumda tamamlanır; burada istisnaları, sağlanan görevlerin her birinden kaydırılmamış istisnalar kümesinin toplanmasını içerecektir.

Sağlanan görevlerden hiçbiri başarısız olmaz, ancak en az biri iptal edilirse, döndürülen görev İptal durumunda sona erer.

Görevlerin hiçbiri başarısız olmazsa ve görevlerin hiçbiri iptal edilmezse, sonuçta ortaya çıkan görev RanToCompletion durumunda sona erer. Sağlanan dizi / numaralandırılabilir görev içermiyorsa, döndürülen görev, çağırana dönmeden hemen önce bir RanToCompletion durumuna geçer.


4
Bunu denediğimde görevlerim sırayla çalışıyor mu? Daha önce her bir göreve bireysel olarak başlamak gerekiyor await Task.WhenAll(task1, task2);mu?
Zapnologica

4
@Zapnologica Task.WhenAllsizin için görevleri başlatmaz. Onlara "sıcak" sağlamanız gerekiyor, yani zaten başlamış.
Yuval Itzchakov

2
Tamam. Mantıklı. Peki örneğiniz ne yapacak? Onlara başlamadığın için mi?
Zapnologica

2
@YuvalItzchakov çok teşekkür ederim! Çok basit ama bugün bana çok yardımcı oldu! En az +1000 değerinde :)
Daniel Dušek

1
@Pierre Takip etmiyorum. Ne yapar StartNewve iplik yeni görevler uyumsuz hepsini bekleyen ilgisi var?
Yuval Itzchakov

106

Şunun gibi birçok görev oluşturabilirsiniz:

List<Task> TaskList = new List<Task>();
foreach(...)
{
   var LastTask = new Task(SomeFunction);
   LastTask.Start();
   TaskList.Add(LastTask);
}

Task.WaitAll(TaskList.ToArray());

48
WhenAll
Ravi

.Start () yerine await anahtar sözcüğünü kullanarak aynı anda birden çok yeni iş parçacığı başlatmak mümkün müdür?
Matt W

1
@MattW Hayır, beklediğinizde tamamlanmasını bekler. Bu durumda, çok iş parçacıklı bir ortam oluşturamazsınız. Döngünün sonunda tüm görevlerin beklemesinin nedeni budur.
Virüs

5
Bunun bir engelleme çağrısı olduğu netleşmediği için gelecekteki okuyucular için aşağı oy verin.
JRoughan

Bunu yapmama nedenleriniz için kabul edilen cevaba bakınız.
EL MOJO

26

Gördüğüm en iyi seçenek aşağıdaki uzantı yöntemidir:

public static Task ForEachAsync<T>(this IEnumerable<T> sequence, Func<T, Task> action) {
    return Task.WhenAll(sequence.Select(action));
}

Buna şöyle deyin:

await sequence.ForEachAsync(item => item.SomethingAsync(blah));

Veya asenkron lambda ile:

await sequence.ForEachAsync(async item => {
    var more = await GetMoreAsync(item);
    await more.FrobbleAsync();
});

26

Sen kullanabilirsiniz WhenAllbir awaitable dönecektir hangi Taskveya WaitAlldönüş türü olan ve daha fazla kod yürütme Simular engeller Thread.Sleepbütün görevleri tamamlanmış, iptal veya hatalı kadar.

resim açıklamasını buraya girin

Misal

var tasks = new Task[] {
    TaskOperationOne(),
    TaskOperationTwo()
};

Task.WaitAll(tasks);
// or
await Task.WhenAll(tasks);

Eğer görevleri pratik bir şekilde yürütmek istiyorsanız, bu anwser'dan ilham alabilirsiniz .


partiye geç geldiğiniz için özür dilerim, ama neden awaither operasyon için ve aynı zamanda kullanıyorsunuz WaitAllveya WhenAll. Task[]Başlatmadaki görevler olmadan olmamalı mı await?
dee zg

@dee zg Haklısın. Yukarıdaki beklenti amacı yendi. Cevabımı değiştirip kaldıracağım.
NtFreX

Evet, bu o. açıklama için teşekkürler! (nice answer için oy verin)
dee zg

8

Zincirleri zincirlemek mi istiyorsunuz Taskyoksa paralel olarak mı çağrılabilirler?

Zincirleme İçin
Sadece böyle bir şey yapmak

Task.Run(...).ContinueWith(...).ContinueWith(...).ContinueWith(...);
Task.Factory.StartNew(...).ContinueWith(...).ContinueWith(...).ContinueWith(...);

ve hatalı olabileceği için Taskher birinde bir önceki örneği kontrol etmeyi unutmayın ContinueWith.

Paralel olarak
Karşılaştığım en basit yöntem: Parallel.Invoke Aksi takdirde , sıfır eylem için geriye sayım yapmak için s'yi Task.WaitAllkullanabilirsiniz ya da kullanabilirsiniz WaitHandle(bekleyin, yeni bir sınıf var:) CountdownEvent, ya da ...


3
Cevabı takdir et, ancak önerileriniz biraz daha açıklanabilirdi.
Daniel Minnaar

@drminnaar msdn bağlantılarının yanında başka hangi açıklamalara ihtiyacınız var? bağlantıları tıklamamıştın, değil mi?
Andreas Niedermair

4
Bağlantıları tıkladım ve içeriği okudum. Ben Invoke için gidiyordu, ama bir sürü If's and But vardı zaman uyumsuz olarak çalışıp çalışmadığı hakkında. Cevabınızı sürekli düzenliyordunuz. Gönderdiğiniz WaitAll bağlantısı mükemmeldi, ancak aynı işlevselliği daha hızlı ve daha kolay okunabilir şekilde gösteren cevaba gittim. Rahatsız etmeyin, cevabınız hala diğer yaklaşımlara iyi alternatifler sunuyor.
Daniel Minnaar

@drminnaar burada suç işlenmedi, ben sadece merak ediyorum :)
Andreas Niedermair

5

Bu nasıl bir dizi Func <> ile yapmak :

var tasks = new Func<Task>[]
{
   () => myAsyncWork1(),
   () => myAsyncWork2(),
   () => myAsyncWork3()
};

await Task.WhenAll(tasks.Select(task => task()).ToArray()); //Async    
Task.WaitAll(tasks.Select(task => task()).ToArray()); //Or use WaitAll for Sync

1
Neden sadece Görev Dizisi olarak kalmıyorsun?
Talha Talip Açıkgöz

1
Eğer dikkatli değilseniz @ talha-talip-açıkgöz görevleri yerine getirmesini beklemediğiniz zaman yerine getirin. Bunu bir Func delegesi olarak yapmak niyetinizi netleştirir.
DalSoft

5

Yine başka bir cevap ... ama genellikle aynı anda veri yüklemek ve gibi değişkenlere koymak gerektiğinde, bir durumda kendimi bulmak:

var cats = new List<Cat>();
var dog = new Dog();

var loadDataTasks = new Task[]
{
    Task.Run(async () => cats = await LoadCatsAsync()),
    Task.Run(async () => dog = await LoadDogAsync())
};

try
{
    await Task.WhenAll(loadDataTasks);
}
catch (Exception ex)
{
    // handle exception
}

1
Eğer LoadCatsAsync()ve LoadDogAsync()sadece veritabanı çağrıları IO bağlı. Task.Run()CPU bağlantılı işler içindir; tüm yaptığınız veritabanı sunucusundan bir yanıt bekliyorsanız, ek gereksiz ek yük ekler. Yuval'ın kabul ettiği cevap IO'lu çalışma için doğru yoldur.
Stephen Kennedy

@StephenKennedy, ne tür genel giderleri ve performansı ne kadar etkileyebileceğini açıklar mısınız? Teşekkürler!
Yehor Hromadskyi

Bu yorum kutusunda özetlemek oldukça zor olurdu :) Bunun yerine Stephen Cleary'nin makalelerini okumanızı tavsiye ederim - o bu konuda uzmandır. Buradan başlayın: blog.stephencleary.com/2013/10/…
Stephen Kennedy

-1

Bu senaryolardan bazıları için görevi nasıl kullanacağınızı göstermek için bir kod parçası hazırladım.

    // method to run tasks in a parallel 
    public async Task RunMultipleTaskParallel(Task[] tasks) {

        await Task.WhenAll(tasks);
    }
    // methode to run task one by one 
    public async Task RunMultipleTaskOneByOne(Task[] tasks)
    {
        for (int i = 0; i < tasks.Length - 1; i++)
            await tasks[i];
    }
    // method to run i task in parallel 
    public async Task RunMultipleTaskParallel(Task[] tasks, int i)
    {
        var countTask = tasks.Length;
        var remainTasks = 0;
        do
        {
            int toTake = (countTask < i) ? countTask : i;
            var limitedTasks = tasks.Skip(remainTasks)
                                    .Take(toTake);
            remainTasks += toTake;
            await RunMultipleTaskParallel(limitedTasks.ToArray());
        } while (remainTasks < countTask);
    }

1
Görevlerin sonuçlarını nasıl alabilirim? Örneğin, bir veri tablosunda "satırları" (paralel olarak N görevden) birleştirme ve gridview asp.net için bağlama?
PreguntonCojoneroCabrón
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.