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.