Await in a loop nasıl kullanılır


87

Bir koleksiyon üzerinde biraz çalışma yapan eşzamansız bir konsol uygulaması oluşturmaya çalışıyorum. Async / await kullanan başka bir sürüm için paralel döngü kullanan bir sürümüm var. Async / await sürümünün paralel sürüme benzer çalışmasını bekliyordum, ancak eşzamanlı olarak çalışıyor. Neyi yanlış yapıyorum?

class Program
{
    static void Main(string[] args)
    {
        var worker = new Worker();
        worker.ParallelInit();
        var t = worker.Init();
        t.Wait();
        Console.ReadKey();
    }
}

public class Worker
{
    public async Task<bool> Init()
    {
        var series = Enumerable.Range(1, 5).ToList();
        foreach (var i in series)
        {
            Console.WriteLine("Starting Process {0}", i);
            var result = await DoWorkAsync(i);
            if (result)
            {
                Console.WriteLine("Ending Process {0}", i);
            }
        }

        return true;
    }

    public async Task<bool> DoWorkAsync(int i)
    {
        Console.WriteLine("working..{0}", i);
        await Task.Delay(1000);
        return true;
    }

    public bool ParallelInit()
    {
        var series = Enumerable.Range(1, 5).ToList();
        Parallel.ForEach(series, i =>
        {
            Console.WriteLine("Starting Process {0}", i);
            DoWorkAsync(i);
            Console.WriteLine("Ending Process {0}", i);
        });
        return true;
    }
}

Yanıtlar:


126

awaitAnahtar kelimeyi kullanma şekliniz , C # 'a döngüden her geçtiğinizde beklemek istediğinizi söyler, bu paralel değildir. İstediğiniz şeyi yapmak için yönteminizi bu şekilde yeniden yazabilir, bir e-posta listesi kaydedebilir Taskve ardından awaithepsini kullanabilirsiniz Task.WhenAll.

public async Task<bool> Init()
{
    var series = Enumerable.Range(1, 5).ToList();
    var tasks = new List<Task<Tuple<int, bool>>>();
    foreach (var i in series)
    {
        Console.WriteLine("Starting Process {0}", i);
        tasks.Add(DoWorkAsync(i));
    }
    foreach (var task in await Task.WhenAll(tasks))
    {
        if (task.Item2)
        {
            Console.WriteLine("Ending Process {0}", task.Item1);
        }
    }
    return true;
}

public async Task<Tuple<int, bool>> DoWorkAsync(int i)
{
    Console.WriteLine("working..{0}", i);
    await Task.Delay(1000);
    return Tuple.Create(i, true);
}

3
Başkalarını bilmiyorum, ancak / foreach için bir paralellik, paralel döngüler için daha basit görünüyor.
Brettski

8
Ending ProcessBildirimi gördüğünüzde , görevin gerçekten sona erdiği zaman olmadığını unutmamak önemlidir . Tüm bu bildirimler , görevlerin sonuncusu tamamlandıktan hemen sonra sırayla atılır. "İşlem 1 Bitiyor" u gördüğünüzde, işlem 1 uzun süredir bitmiş olabilir. Orada kelime seçimi dışında +1.
Asad Saeeduddin

@Brettski Yanılıyor olabilirim, ancak paralel bir döngü her türlü asenkron sonucu yakalar. Bir Task <T> döndürerek, iptal etme veya istisnaları görme gibi içeride devam eden işleri yönetebileceğiniz bir Task nesnesini hemen geri alırsınız. Artık Async / Await ile Task nesnesiyle daha kolay bir şekilde çalışabilirsiniz - yani Task.Result yapmak zorunda değilsiniz.
The Muffin Man

@Tim S, Tasks.WhenAll yöntemini kullanarak zaman uyumsuz işlevli bir değer döndürmek istersem ne olur?
Mihir

Maksimum yürütme görevlerini sınırlamak için bir Semaphorein uygulamak kötü bir uygulama olur DoWorkAsyncmu?
C4d

39

Kodunuz await, bir sonraki yinelemeye başlamadan önce her işlemin (kullanılarak ) bitmesini bekler .
Bu nedenle, herhangi bir paralellik elde edemezsiniz.

Mevcut bir eşzamansız işlemi paralel olarak çalıştırmak istiyorsanız buna ihtiyacınız yoktur await; yalnızca bir e-posta koleksiyonu almanız Taskve Task.WhenAll()hepsini bekleyen bir görevi döndürmek için aramanız gerekir:

return Task.WhenAll(list.Select(DoWorkAsync));

yani herhangi bir döngüde eşzamansız yöntemler kullanamazsınız?
Cts

4
@Satish: Yapabilirsin. Ancak, awaitbu - ne istediğinizi tam tersini yapar bekler için Taskbitirmek için.
SLaks

Cevabınızı kabul etmek istedim ama Tims S'nin daha iyi bir cevabı var.
Cts

Veya görevin ne zaman bittiğini bilmeniz gerekmiyorsa, yöntemleri beklemeden çağırabilirsiniz
disklosr

Sözdiziminin ne yaptığını doğrulamak için - içindeki DoWorkAsyncher öğe için çağrılan Görevi çalıştırıyor list(her öğenin DoWorkAsynctek bir parametresi olduğunu varsaydığım her öğeyi içeri aktarıyorum)
jbyrd

12
public async Task<bool> Init()
{
    var series = Enumerable.Range(1, 5);
    Task.WhenAll(series.Select(i => DoWorkAsync(i)));
    return true;
}

4

C # 7.0'da , demet üyelerinden her biri için anlamsal adlar kullanabilirsiniz , işte Tim S.'nin yeni sözdizimini kullanarak cevabı:

public async Task<bool> Init()
{
    var series = Enumerable.Range(1, 5).ToList();
    var tasks = new List<Task<(int Index, bool IsDone)>>();

    foreach (var i in series)
    {
        Console.WriteLine("Starting Process {0}", i);
        tasks.Add(DoWorkAsync(i));
    }

    foreach (var task in await Task.WhenAll(tasks))
    {
        if (task.IsDone)
        {
            Console.WriteLine("Ending Process {0}", task.Index);
        }
    }

    return true;
}

public async Task<(int Index, bool IsDone)> DoWorkAsync(int i)
{
    Console.WriteLine("working..{0}", i);
    await Task.Delay(1000);
    return (i, true);
}

task. İçeridenforeach de kurtulabilirsiniz :

// ...
foreach (var (IsDone, Index) in await Task.WhenAll(tasks))
{
    if (IsDone)
    {
        Console.WriteLine("Ending Process {0}", Index);
    }
}
// ...
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.