Chạy song song hai tác vụ không đồng bộ và thu thập kết quả trong .NET 4.5


116

Tôi đã cố gắng trong một thời gian để có được thứ mà tôi nghĩ sẽ đơn giản làm việc với .NET 4.5

Tôi muốn loại bỏ hai tác vụ đang chạy dài cùng lúc và thu thập
kết quả theo cách tốt nhất C # 4.5 (RTM)

Những điều sau đây hoạt động nhưng tôi không thích nó vì:

  • Tôi muốn Sleeptrở thành một phương thức không đồng bộ để nó có thể awaitcác phương thức khác
  • Nó chỉ trông vụng về với Task.Run()
  • Tôi không nghĩ rằng điều này thậm chí đang sử dụng bất kỳ tính năng ngôn ngữ mới nào cả!

Mã làm việc:

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

Mã không hoạt động:

Cập nhật: Điều này thực sự hoạt động và là cách chính xác để làm điều đó, vấn đề duy nhất là Thread.Sleep

Mã này không hoạt động vì lệnh gọi Sleep(5000)bắt đầu ngay tác vụ đang chạy nên Sleep(1000)không chạy cho đến khi nó hoàn thành. Điều này đúng cho dù Sleepasyncvà tôi không sử dụng awaithoặc gọi điện thoại .Resultquá sớm.

Tôi nghĩ có lẽ có một cách nào đó để gỡ lỗi không chạy Task<T>bằng cách gọi một asyncphương thức để sau đó tôi có thể gọi Start()hai tác vụ, nhưng tôi không thể tìm ra cách lấy một Task<T>phương thức không đồng bộ.

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

lưu ý: làm cho Go trở thành một phương thức không đồng bộ không có gì khác biệt
Simon_Weaver

3
Chặn đang xảy ra lúc task1.Resultkhông phải var task1 = Sleep(5000)vì phương pháp Sleep của bạn không có từ khóa await là đồng bộ.
Arvis 14/1213

Câu trả lời:


86

Bạn nên sử dụng Task.Delay thay vì Sleep để lập trình không đồng bộ và sau đó sử dụng Task.WhenAll để kết hợp các kết quả tác vụ. Các nhiệm vụ sẽ chạy song song.

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
Đây là một câu trả lời tuyệt vời ... nhưng tôi đã nghĩ đây là câu trả lời sai cho đến khi tôi chạy nó. sau đó tôi đã hiểu. Nó thực sự thực thi trong 5 giây. Bí quyết là KHÔNG chờ đợi các nhiệm vụ ngay lập tức, thay vào đó hãy chờ đợi trên Task.WhenAll.
Tim Lovell-Smith

113
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
Tôi +1 vì bạn khai báo t1, t2 là Task, đó là cách làm đúng.
Minime

12
Tôi tin rằng giải pháp này yêu cầu phương thức Go cũng phải không đồng bộ, có nghĩa là nó có khả năng không đồng bộ. Nếu bạn muốn một cái gì đó giống như trường hợp người hỏi trong đó Gophương thức của người gọi là đồng bộ, nhưng muốn hoàn thành hai tác vụ độc lập không đồng bộ (nghĩa là không cần phải hoàn thành trước nhiệm vụ kia, nhưng cả hai phải hoàn thành trước khi thực thi tiếp tục) thì Task.WaitAlltốt hơn, và bạn không 'không cần từ khóa await, do đó không cần Gophương thức gọi là không đồng bộ hóa chính nó. Cả hai cách tiếp cận đều không tốt hơn, vấn đề chỉ là mục tiêu của bạn là gì.
AaronLS

1
Void methode: async void LongTask1() {...}không có thuộc tính Task.Result. Sử dụng Task mà không T trong trường hợp như vậy: async Task LongTask1().
Arvis 14/1213

Tôi không nhận được kết quả từ một trong hai nhiệm vụ. Vì vậy, tôi đã thay đổi nó thành Task<TResult> t1 = LongTask1();và bây giờ tôi nhận được t1.Result. <TResult>là loại kết quả trả về của bạn. Bạn sẽ cần một return <TResult>phương pháp của mình để điều này hoạt động.
gilu

1
Có thể đáng nói là nếu bạn đang làm một số việc thực sự đơn giản và không muốn các biến phụ t1t2biến, bạn có thể sử dụng new Task(...). Ví dụ: int int1 = 0; await Task.WhenAll(new Task(async () => { int1 = await LongTask1(); }));. Một điểm nổi bật của phương pháp này là trình biên dịch sẽ không nhận ra rằng biến đã được gán cho và sẽ coi nó là chưa được gán nếu bạn không cấp cho nó một giá trị ban đầu.
Robert Dennis

3

Trong khi Sleepphương pháp của bạn Thread.Sleeplà không đồng bộ , thì không. Toàn bộ ý tưởng của async là sử dụng lại một luồng duy nhất, không bắt đầu nhiều luồng. Bởi vì bạn đã chặn sử dụng một cuộc gọi đồng bộ tới Thread.Sleep, nó sẽ không hoạt động.

Tôi cho rằng đó Thread.Sleeplà sự đơn giản hóa những gì bạn thực sự muốn làm. Việc triển khai thực tế của bạn có thể được mã hóa dưới dạng các phương thức không đồng bộ không?

Nếu bạn cần chạy nhiều cuộc gọi chặn đồng bộ, tôi nghĩ rằng hãy tìm ở nơi khác!


cảm ơn Richard - vâng, nó có vẻ hoạt động như mong đợi khi tôi thực sự sử dụng cuộc gọi dịch vụ của mình
Simon_Weaver

sau đó làm thế nào để chạy async? Tôi có ứng dụng thực hiện rất nhiều chuyển đổi tệp và đợi tệp, khoảng 5 giây và sau đó là một quá trình khác, khi tôi "khi nào cho tất cả" thì nó chạy lần đầu tiên, rồi lần thứ hai, mặc dù tôi đã nói: var x = y()và không var x=await y()hoặc y().wait()vẫn vậy. đợi tất cả các cách và nếu async không tự xử lý điều đó, tôi nên làm gì? LƯU Ý rằng y được trang trí bằng không đồng bộ và tôi hy vọng nó sẽ làm tất cả trong thời gian tất cả, không phải ở nơi nó được chỉ định, CHỈNH SỬA: tôi chỉ khi đối tác của tôi nói, hãy thử Task.Factoryvà anh ấy nói rằng nó hoạt động khi tôi thoát ra bên của lớp học này
deadManN

2

Để trả lời điểm này:

Tôi muốn Sleep là một phương thức không đồng bộ để nó có thể chờ các phương thức khác

bạn có thể viết lại Sleephàm như thế này:

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

chạy mã này sẽ xuất ra:

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

2

Đó là cuối tuần bây giờ!

    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

Bài báo này đã giúp giải thích rất nhiều điều. Nó ở dạng FAQ.

Câu hỏi thường gặp về Async / Await

Phần này giải thích tại sao lại Thread.Sleepchạy trên cùng một chủ đề gốc - dẫn đến sự nhầm lẫn ban đầu của tôi.

Từ khóa “async” có khiến việc gọi một phương thức vào hàng đợi ThreadPool không? Để tạo một chủ đề mới? Để phóng tàu tên lửa lên sao Hỏa?

Không. Và không. Xem các câu hỏi trước. Từ khóa “async” cho trình biên dịch biết rằng “await” có thể được sử dụng bên trong phương thức, như vậy phương thức có thể tạm dừng tại một điểm đang chờ và việc thực thi của nó được tiếp tục không đồng bộ khi phiên bản chờ hoàn tất. Đây là lý do tại sao trình biên dịch đưa ra cảnh báo nếu không có "chờ" bên trong một phương thức được đánh dấu là "không đồng bộ".

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.