รันงาน async สองงานพร้อมกันและรวบรวมผลลัพธ์ใน. NET 4.5


116

ฉันพยายามมาระยะหนึ่งแล้วเพื่อหาสิ่งที่ฉันคิดว่าจะใช้งานได้ง่ายกับ. NET 4.5

ฉันต้องการปิดงานสองงานที่ทำงานเป็นเวลานานในเวลาเดียวกันและรวบรวม
ผลลัพธ์ด้วยวิธี C # 4.5 (RTM) ที่ดีที่สุด

ต่อไปนี้ใช้งานได้ แต่ฉันไม่ชอบเพราะ:

  • ฉันต้องการSleepเป็นวิธี async เพื่อให้สามารถใช้awaitวิธีอื่นได้
  • มันดูเงอะงะด้วย Task.Run()
  • ฉันไม่คิดว่านี่เป็นการใช้คุณสมบัติภาษาใหม่ ๆ เลยด้วยซ้ำ!

รหัสการทำงาน:

public static void Go()
{
    Console.WriteLine("Starting");

    var task1 = Task.Run(() => Sleep(5000));    
    var task2 = Task.Run(() => Sleep(3000));

    int totalSlept = task1.Result + task2.Result;

    Console.WriteLine("Slept for a total of " + totalSlept + " ms");
}

private static int Sleep(int ms)
{
    Console.WriteLine("Sleeping for " + ms);
    Thread.Sleep(ms);
    Console.WriteLine("Sleeping for " + ms + " FINISHED");
    return ms;
}

รหัสที่ไม่ทำงาน:

อัปเดต: วิธีนี้ใช้งานได้จริงและเป็นวิธีที่ถูกต้องปัญหาเดียวคือไฟล์ Thread.Sleep

รหัสนี้ใช้ไม่ได้เนื่องจากการเรียกให้Sleep(5000)เริ่มงานที่รันทันทีจึงSleep(1000)ไม่ทำงานจนกว่าจะเสร็จสิ้น นี่เป็นเรื่องจริงแม้ว่าจะSleepเป็นasyncและฉันไม่ได้ใช้awaitหรือโทร.Resultเร็วเกินไป

ฉันคิดว่าอาจมีวิธีที่จะทำให้การไม่ทำงานTask<T>โดยการเรียกใช้asyncเมธอดดังนั้นฉันจึงสามารถเรียกStart()ใช้งานทั้งสองได้ แต่ฉันคิดไม่ออกว่าจะTask<T>เรียกวิธีการ async ได้อย่างไร

public static void Go()
{
    Console.WriteLine("Starting");

    var task1 = Sleep(5000);    // blocks
    var task2 = Sleep(1000);

    int totalSlept = task1.Result + task2.Result;

    Console.WriteLine("Slept for " + totalSlept + " ms");
}

private static async Task<int> Sleep(int ms)
{
    Console.WriteLine("Sleeping for " + ms);
    Thread.Sleep(ms);
    return ms;
}

หมายเหตุ: การทำให้ Go เป็นวิธี async ไม่แตกต่างกัน
Simon_Weaver

3
บล็อกที่เกิดขึ้นที่task1.Resultไม่ได้var task1 = Sleep(5000)เนื่องจากวิธีการนอนหลับของคุณได้โดยไม่ต้องรอคอยคำหลักคือซิงโคร
Arvis

คำตอบ:


86

คุณควรใช้ Task.Delay แทน Sleep สำหรับการเขียนโปรแกรม async จากนั้นใช้ Task whenAll เพื่อรวมผลลัพธ์ของงาน งานจะทำงานแบบขนาน

public class Program
    {
        static void Main(string[] args)
        {
            Go();
        }
        public static void Go()
        {
            GoAsync();
            Console.ReadLine();
        }
        public static async void GoAsync()
        {

            Console.WriteLine("Starting");

            var task1 = Sleep(5000);
            var task2 = Sleep(3000);

            int[] result = await Task.WhenAll(task1, task2);

            Console.WriteLine("Slept for a total of " + result.Sum() + " ms");

        }

        private async static Task<int> Sleep(int ms)
        {
            Console.WriteLine("Sleeping for {0} at {1}", ms, Environment.TickCount);
            await Task.Delay(ms);
            Console.WriteLine("Sleeping for {0} finished at {1}", ms, Environment.TickCount);
            return ms;
        }
    }

11
นี่เป็นคำตอบที่ดี ... แต่ฉันคิดว่าคำตอบนี้ผิดจนกว่าฉันจะวิ่งมัน แล้วฉันก็เข้าใจ มันทำงานได้จริงใน 5 วินาที เคล็ดลับคืออย่ารองานในทันที แต่ให้รอ Task WhenAll แทน
Tim Lovell-Smith

114
async Task<int> LongTask1() { 
  ...
  return 0; 
}

async Task<int> LongTask2() { 
  ...
  return 1; 
}

...
{
   Task<int> t1 = LongTask1();
   Task<int> t2 = LongTask2();
   await Task.WhenAll(t1,t2);
   //now we have t1.Result and t2.Result
}

2
ฉัน +1 เพราะคุณประกาศ t1, t2 เป็น Task ซึ่งเป็นวิธีที่ถูกต้อง
Minime

12
ฉันเชื่อว่าโซลูชันนี้ต้องใช้เมธอด Go เพื่อให้เป็นแบบอะซิงโครนัสซึ่งหมายความว่าจะแสดงความสามารถในการอะซิงโครนัส หากคุณต้องการบางอย่างมากขึ้นเช่นกรณี askers ที่Goวิธีการของผู้โทรเป็นแบบซิงโครนัส แต่ต้องการทำงานสองอย่างที่เป็นอิสระแบบอะซิงโครนัสให้เสร็จสมบูรณ์ (เช่นไม่จำเป็นต้องทำให้เสร็จก่อนงานอื่น แต่ทั้งสองต้องเสร็จสิ้นก่อนที่การดำเนินการจะดำเนินต่อไป) Task.WaitAllจะเป็นการดีกว่าและคุณไม่ต้องทำ ไม่จำเป็นต้องใช้คีย์เวิร์ดรอดังนั้นไม่จำเป็นต้องใช้Goวิธีการโทรเพื่อเป็น async เอง ทั้งสองแนวทางไม่ดีกว่า แต่เป็นเพียงเป้าหมายของคุณ
AaronLS

1
โมฆะเมทโธ: async void LongTask1() {...}ไม่มีคุณสมบัติ Task.Result ใช้งานโดยไม่มี T ในกรณีเช่นนี้: async Task LongTask1().
Arvis

ฉันไม่ได้รับผลลัพธ์จากงานใดงานหนึ่ง ดังนั้นผมจึงเปลี่ยนไปและตอนนี้ฉันได้รับTask<TResult> t1 = LongTask1(); คือประเภทการส่งคืนผลลัพธ์ของคุณ คุณจะต้องใช้วิธีการของคุณเพื่อให้ได้ผล t1.Result<TResult>return <TResult>
gilu

1
อาจเป็นเรื่องที่ควรค่าแก่การกล่าวถึงว่าหากคุณกำลังทำสิ่งที่ง่ายจริงๆและไม่ต้องการสิ่งพิเศษt1และt2ตัวแปรคุณสามารถnew Task(...)ใช้ได้ ตัวอย่างเช่น: int int1 = 0; await Task.WhenAll(new Task(async () => { int1 = await LongTask1(); }));. ข้อสังเกตประการหนึ่งของแนวทางนี้คือคอมไพลเลอร์จะไม่รับรู้ว่าตัวแปรนั้นถูกกำหนดให้และจะถือว่าไม่ได้กำหนดหากคุณไม่ให้ค่าเริ่มต้น
Robert Dennis

3

ในขณะที่Sleepวิธีของคุณเป็นแบบ asyncThread.Sleepก็ไม่ใช่ แนวคิดทั้งหมดของ async คือการใช้เธรดเดียวซ้ำไม่ใช่การเริ่มต้นหลายเธรด เนื่องจากคุณได้บล็อกโดยใช้การโทรแบบซิงโครนัสไปยัง Thread.Sleep จึงไม่ทำงาน

ฉันคิดว่านั่นThread.Sleepเป็นการทำให้สิ่งที่คุณต้องการทำนั้นง่ายขึ้น การใช้งานจริงของคุณสามารถเข้ารหัสเป็นวิธีการ async ได้หรือไม่

หากคุณต้องการเรียกใช้การบล็อกซิงโครนัสหลายสายฉันคิดว่าดูที่อื่นสิ!


ขอบคุณ Richard - ใช่ดูเหมือนว่าจะทำงานได้ตามที่คาดไว้เมื่อฉันใช้บริการจริง
Simon_Weaver

แล้วจะเรียกใช้ async ได้อย่างไร? ฉันมีแอปพลิเคชั่นที่สลับไฟล์จำนวนมากและรอไฟล์ประมาณ 5 วินาทีจากนั้นอีกขั้นตอนหนึ่งเมื่อฉัน "เมื่อใด" ทั้งหมดจะเรียกใช้ครั้งแรกก่อนจากนั้นเป็นครั้งที่สองแม้ว่าฉันจะพูดว่า: แต่ก็ยังvar x = y()ไม่ใช่var x=await y()หรือy().wait()ยัง รอตลอดทางและหาก async ไม่สามารถจัดการได้ด้วยตัวเองฉันควรทำอย่างไร? โปรดทราบว่า y ได้รับการตกแต่งด้วย async และฉันคาดหวังว่ามันจะทำทุกอย่างภายในเมื่อทั้งหมดไม่ถูกต้องตามที่กำหนดแก้ไข: ฉันแค่บอกว่าเมื่อไหร่ที่คู่ของฉันพูดว่าลองดูTask.Factoryและเขาบอกว่ามันได้ผลเมื่อฉันแตกออก ด้านข้างของคลาสนี้
deadManN

2

เพื่อตอบประเด็นนี้:

ฉันต้องการให้ Sleep เป็นวิธี async เพื่อรอวิธีอื่น ๆ

คุณอาจเขียนSleepฟังก์ชันใหม่ได้เช่นนี้:

private static async Task<int> Sleep(int ms)
{
    Console.WriteLine("Sleeping for " + ms);
    var task = Task.Run(() => Thread.Sleep(ms));
    await task;
    Console.WriteLine("Sleeping for " + ms + "END");
    return ms;
}

static void Main(string[] args)
{
    Console.WriteLine("Starting");

    var task1 = Sleep(2000);
    var task2 = Sleep(1000);

    int totalSlept = task1.Result +task2.Result;

    Console.WriteLine("Slept for " + totalSlept + " ms");
    Console.ReadKey();
}

การเรียกใช้รหัสนี้จะส่งออก:

Starting
Sleeping for 2000
Sleeping for 1000
*(one second later)*
Sleeping for 1000END
*(one second later)*
Sleeping for 2000END
Slept for 3000 ms

2

มันเป็นวันหยุดสุดสัปดาห์ในขณะนี้!

    public async void Go()
    {
        Console.WriteLine("Start fosterage...");

        var t1 = Sleep(5000, "Kevin");
        var t2 = Sleep(3000, "Jerry");
        var result = await Task.WhenAll(t1, t2);

        Console.WriteLine($"My precious spare time last for only {result.Max()}ms");
        Console.WriteLine("Press any key and take same beer...");
        Console.ReadKey();
    }

    private static async Task<int> Sleep(int ms, string name)
    {
            Console.WriteLine($"{name} going to sleep for {ms}ms :)");
            await Task.Delay(ms);
            Console.WriteLine("${name} waked up after {ms}ms :(";
            return ms;
    }

0

บทความนี้ช่วยอธิบายหลายสิ่งหลายอย่าง ในรูปแบบ FAQ

คำถามที่พบบ่อยของ Async / Await

ส่วนนี้อธิบายว่าเหตุใดจึงThread.Sleepทำงานบนเธรดต้นฉบับเดียวกันซึ่งทำให้ฉันสับสนในตอนแรก

คีย์เวิร์ด“ async” ทำให้การเรียกใช้เมธอดเข้าคิวไปยัง ThreadPool หรือไม่ หากต้องการสร้างเธรดใหม่ เพื่อส่งยานส่งจรวดไปยังดาวอังคาร?

ไม่ไม่และไม่ ดูคำถามก่อนหน้านี้ คีย์เวิร์ด "async" ระบุให้คอมไพลเลอร์ทราบว่า "รอ" อาจถูกใช้ภายในเมธอดซึ่งเมธอดนั้นอาจหยุดชั่วคราวที่จุดรอคอยและให้การดำเนินการกลับมาทำงานแบบอะซิงโครนัสต่อเมื่ออินสแตนซ์ที่รอคอยเสร็จสิ้น นี่คือสาเหตุที่คอมไพลเลอร์ออกคำเตือนหากไม่มี "รอ" อยู่ในเมธอดที่ระบุว่า "async"

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.