วิธีใช้รอวนซ้ำ


87

ฉันกำลังพยายามสร้างแอปคอนโซลแบบอะซิงโครนัสที่ทำงานบางอย่างกับคอลเลกชัน ฉันมีเวอร์ชันหนึ่งที่ใช้แบบขนานสำหรับลูปเวอร์ชันอื่นที่ใช้ async / await ฉันคาดว่าเวอร์ชัน async / await จะทำงานคล้ายกับเวอร์ชันคู่ขนาน แต่ดำเนินการพร้อมกัน ผมทำอะไรผิดหรือเปล่า?

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;
    }
}

คำตอบ:


126

วิธีที่คุณใช้awaitคีย์เวิร์ดจะบอก C # ว่าคุณต้องการรอทุกครั้งที่คุณผ่านลูปซึ่งไม่ขนานกัน คุณสามารถเขียนวิธีการของคุณใหม่เช่นนี้เพื่อทำสิ่งที่คุณต้องการโดยการจัดเก็บรายการTasks แล้วawaitรวมเข้าด้วย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
ฉันไม่รู้เกี่ยวกับคนอื่น แต่คู่ขนานสำหรับ / foreach ดูเหมือนตรงไปข้างหน้ามากกว่าสำหรับลูปขนาน
Brettski

8
โปรดทราบว่าเมื่อคุณเห็นการEnding Processแจ้งเตือนไม่ใช่เวลาที่งานกำลังจะสิ้นสุดลง การแจ้งเตือนทั้งหมดจะถูกทิ้งตามลำดับทันทีหลังจากงานสุดท้ายเสร็จสิ้น เมื่อคุณเห็น "สิ้นสุดกระบวนการ 1" แสดงว่ากระบวนการ 1 อาจจบลงไปนานแล้ว นอกเหนือจากการเลือกคำตรงนั้น +1
Asad Saeeduddin

@Brettski ฉันอาจจะผิด แต่ลูปคู่ขนานกับดักผล async ทุกประเภท เมื่อส่งคืนงาน <T> คุณจะได้รับวัตถุงานกลับคืนมาทันทีซึ่งคุณสามารถจัดการงานที่เกิดขึ้นภายในเช่นการยกเลิกหรือเห็นข้อยกเว้น ขณะนี้ด้วย Async / Await คุณสามารถทำงานกับวัตถุงานได้อย่างเป็นมิตรมากขึ้นนั่นคือคุณไม่จำเป็นต้องทำภารกิจผลลัพธ์
The Muffin Man

@ Tim S จะทำอย่างไรถ้าฉันต้องการส่งคืนค่าด้วยฟังก์ชัน async โดยใช้เมธอด Tasks whenAll
Mihir

มันจะเป็นปฏิบัติไม่ดีที่จะใช้SemaphoreในDoWorkAsyncการ จำกัด สูงสุดดำเนินงาน?
C4d

39

รหัสของคุณรอให้แต่ละการดำเนินการ (โดยใช้await) เสร็จสิ้นก่อนที่จะเริ่มการทำซ้ำครั้งถัดไป
ดังนั้นคุณจะไม่ได้รับความเท่าเทียมกัน

หากคุณต้องการที่จะเรียกใช้การดำเนินการไม่ตรงกันที่มีอยู่ในแบบคู่ขนานคุณไม่จำเป็นต้องawait; คุณเพียงแค่ต้องได้รับคอลเลกชันTaskและโทรTask.WhenAll()กลับเพื่อส่งคืนงานที่รอให้พวกเขาทั้งหมด:

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

คุณจึงไม่สามารถใช้วิธีอะซิงโครนัสในลูปใด ๆ ได้?
เสาร์

4
@Satish: คุณทำได้ แต่awaitไม่ตรงข้ามกับสิ่งที่คุณต้องการ - มันรอสำหรับTaskที่จะเสร็จสิ้น
SLaks

ฉันต้องการยอมรับคำตอบของคุณ แต่ Tims S มีคำตอบที่ดีกว่า
เสาร์

หรือถ้าคุณไม่จำเป็นต้องรู้ว่าเมื่องานเสร็จสิ้นคุณสามารถเรียกใช้เมธอดได้โดยไม่ต้องรอพวกเขา
disklosr

เพื่อยืนยันว่าไวยากรณ์นั้นกำลังทำอะไร - มันกำลังเรียกใช้งานที่เรียกDoWorkAsyncในแต่ละรายการในlist(ส่งผ่านแต่ละรายการไปDoWorkAsyncซึ่งฉันถือว่ามีพารามิเตอร์เดียว)?
jbyrd


4

ใน C # 7.0 คุณสามารถใช้ชื่อทางความหมายกับสมาชิกแต่ละคนของทูเปิลนี่คือคำตอบของTim S.โดยใช้ไวยากรณ์ใหม่:

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. ภายในforeach :

// ...
foreach (var (IsDone, Index) in await Task.WhenAll(tasks))
{
    if (IsDone)
    {
        Console.WriteLine("Ending Process {0}", Index);
    }
}
// ...
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.