Chạy nhiều tác vụ không đồng bộ và chờ tất cả hoàn thành


265

Tôi cần chạy nhiều tác vụ không đồng bộ trong ứng dụng bảng điều khiển và đợi tất cả hoàn thành trước khi xử lý thêm.

Có nhiều bài viết ngoài kia, nhưng tôi dường như càng bối rối hơn khi đọc. Tôi đã đọc và hiểu các nguyên tắc cơ bản của thư viện Tác vụ, nhưng rõ ràng tôi đang thiếu một liên kết ở đâu đó.

Tôi hiểu rằng có thể xâu chuỗi các nhiệm vụ để chúng bắt đầu sau khi hoàn thành một nhiệm vụ khác (đó là kịch bản cho tất cả các bài viết tôi đã đọc), nhưng tôi muốn tất cả các Nhiệm vụ của mình chạy cùng một lúc và tôi muốn biết một lần tất cả đã hoàn thành

Cách thực hiện đơn giản nhất cho một kịch bản như thế này là gì?

Câu trả lời:


440

Cả hai câu trả lời đều không đề cập đến sự chờ đợi Task.WhenAll:

var task1 = DoWorkAsync();
var task2 = DoMoreWorkAsync();

await Task.WhenAll(task1, task2);

Sự khác biệt chính giữa Task.WaitAllTask.WhenAlllà cái trước sẽ chặn (tương tự như sử dụng Waittrên một tác vụ) trong khi cái sau sẽ không và có thể được chờ đợi, mang lại quyền điều khiển cho người gọi cho đến khi tất cả các tác vụ kết thúc.

Hơn nữa, xử lý ngoại lệ khác nhau:

Task.WaitAll:

Ít nhất một trong số các trường hợp Nhiệm vụ đã bị hủy - hoặc một ngoại lệ đã được đưa ra trong quá trình thực thi ít nhất một trong các trường hợp Nhiệm vụ. Nếu một tác vụ đã bị hủy, AggregateException sẽ chứa một Activity HủyedException trong bộ sưu tập InternalExceptions của nó.

Task.WhenAll:

Nếu bất kỳ tác vụ được cung cấp nào hoàn thành ở trạng thái bị lỗi, thì tác vụ được trả lại cũng sẽ hoàn thành ở trạng thái Bị lỗi, trong đó các ngoại lệ của nó sẽ chứa tập hợp các ngoại lệ không được bao bọc từ mỗi tác vụ được cung cấp.

Nếu không có tác vụ được cung cấp nào bị lỗi nhưng ít nhất một trong số chúng đã bị hủy, tác vụ được trả lại sẽ kết thúc ở trạng thái Đã hủy.

Nếu không có tác vụ nào bị lỗi và không có tác vụ nào bị hủy, tác vụ kết quả sẽ kết thúc ở trạng thái RanToCompletion. Nếu mảng / liệt kê được cung cấp không chứa tác vụ, tác vụ được trả về sẽ ngay lập tức chuyển sang trạng thái RanToCompletion trước khi nó được trả về cho người gọi.


4
Khi tôi thử điều này, nhiệm vụ của tôi chạy tuần tự? Có ai phải bắt đầu từng nhiệm vụ trước đây await Task.WhenAll(task1, task2);không?
Zapnologica

4
@Zapnologica Task.WhenAllkhông bắt đầu nhiệm vụ cho bạn. Bạn phải cung cấp cho họ "nóng", nghĩa là đã bắt đầu.
Yuval Itzchakov

2
Đồng ý. Điều đó có ý nghĩa. Vậy ví dụ của bạn sẽ làm gì? Bởi vì bạn chưa bắt đầu chúng?
Zapnologica

2
@YuvalItzchakov cảm ơn bạn rất nhiều! Nó rất đơn giản nhưng nó đã giúp tôi rất nhiều ngày hôm nay! Có giá trị ít nhất +1000 :)
Daniel Dušek

1
@Pierre Tôi không theo dõi. Những StartNewnhiệm vụ mới và quay vòng phải làm gì với việc chờ đợi không đồng bộ trên tất cả chúng?
Yuval Itzchakov

106

Bạn có thể tạo nhiều tác vụ như:

List<Task> TaskList = new List<Task>();
foreach(...)
{
   var LastTask = new Task(SomeFunction);
   LastTask.Start();
   TaskList.Add(LastTask);
}

Task.WaitAll(TaskList.ToArray());

48
Tôi muốn giới thiệu Khi Tất cả
Ravi

Có thể bắt đầu nhiều chủ đề mới, cùng một lúc, bằng cách sử dụng từ khóa đang chờ chứ không phải .Start ()?
Matt W

1
@MattW Không, khi bạn sử dụng await, nó sẽ đợi nó hoàn thành. Trong trường hợp này, bạn sẽ không thể tạo một môi trường đa luồng. Đây là lý do mà tất cả các nhiệm vụ được chờ ở cuối vòng lặp.
Virus

5
Downvote cho độc giả trong tương lai vì không rõ ràng rằng đây là một cuộc gọi chặn.
JRoughan

Xem câu trả lời được chấp nhận cho lý do tại sao không làm điều này.
EL MOJO

26

Tùy chọn tốt nhất tôi từng thấy là phương pháp mở rộng sau:

public static Task ForEachAsync<T>(this IEnumerable<T> sequence, Func<T, Task> action) {
    return Task.WhenAll(sequence.Select(action));
}

Gọi nó như thế này:

await sequence.ForEachAsync(item => item.SomethingAsync(blah));

Hoặc với lambda async:

await sequence.ForEachAsync(async item => {
    var more = await GetMoreAsync(item);
    await more.FrobbleAsync();
});

26

Bạn có thể sử dụng WhenAllcái nào sẽ trả về một cái đang chờ Taskhoặc WaitAllkhông có kiểu trả về và sẽ chặn mô hình thực thi mã tiếp theo Thread.Sleepcho đến khi tất cả các tác vụ được hoàn thành, hủy bỏ hoặc bị lỗi.

nhập mô tả hình ảnh ở đây

Thí dụ

var tasks = new Task[] {
    TaskOperationOne(),
    TaskOperationTwo()
};

Task.WaitAll(tasks);
// or
await Task.WhenAll(tasks);

Nếu bạn muốn chạy các nhiệm vụ theo thứ tự praticular, bạn có thể lấy cảm hứng từ anwser này .


xin lỗi vì đến bữa tiệc muộn nhưng, tại sao bạn có awaitcho mỗi hoạt động và đồng thời sử dụng WaitAllhay WhenAll. Không nên thực hiện các nhiệm vụ Task[]khởi tạo await?
dee zg

@dee zg Bạn nói đúng. Sự chờ đợi ở trên đánh bại mục đích. Tôi sẽ thay đổi câu trả lời của tôi và loại bỏ chúng.
NtFreX

vâng, đó là nó. cảm ơn đã làm rõ! (upvote cho câu trả lời hay)
dee zg

8

Bạn có muốn xâu chuỗi các Tasks, hoặc chúng có thể được gọi theo cách song song không?

Để xích
Chỉ cần làm một cái gì đó như

Task.Run(...).ContinueWith(...).ContinueWith(...).ContinueWith(...);
Task.Factory.StartNew(...).ContinueWith(...).ContinueWith(...).ContinueWith(...);

và đừng quên kiểm tra Taskví dụ trước trong mỗi trường hợp ContinueWithvì nó có thể bị lỗi.

Đối với cách thức song song
Phương pháp đơn giản nhất mà tôi đã gặp: Parallel.Invoke Nếu không thì cóTask.WaitAll hoặc thậm chí bạn có thể sử dụng WaitHandles để thực hiện đếm ngược đến 0 hành động còn lại (chờ, có một lớp mới CountdownEvent:) hoặc ...


3
Đánh giá cao câu trả lời, nhưng đề xuất của bạn có thể đã được giải thích thêm một chút.
Daniel Minnaar

@drminnaar giải thích nào khác bên cạnh các liên kết đến msdn với các ví dụ bạn cần? bạn thậm chí không nhấp vào liên kết, phải không?
Andreas Niedermair

4
Tôi nhấp vào liên kết, và tôi đọc nội dung. Tôi đã tham gia Invoke, nhưng có rất nhiều If và But về việc nó có chạy không đồng bộ hay không. Bạn đã chỉnh sửa câu trả lời của bạn liên tục. Liên kết WaitAll mà bạn đã đăng là hoàn hảo, nhưng tôi đã đi đến câu trả lời thể hiện chức năng tương tự theo cách nhanh hơn và dễ đọc hơn. Đừng xúc phạm, câu trả lời của bạn vẫn cung cấp các lựa chọn thay thế tốt cho các phương pháp khác.
Daniel Minnaar

@drminnaar không có hành vi phạm tội ở đây, tôi chỉ tò mò :)
Andreas Niedermair

5

Đây là cách tôi thực hiện với một mảng Func <> :

var tasks = new Func<Task>[]
{
   () => myAsyncWork1(),
   () => myAsyncWork2(),
   () => myAsyncWork3()
};

await Task.WhenAll(tasks.Select(task => task()).ToArray()); //Async    
Task.WaitAll(tasks.Select(task => task()).ToArray()); //Or use WaitAll for Sync

1
Tại sao bạn không giữ nó làm Nhiệm vụ?
Talha Talip Açıkgöz

1
Nếu bạn không cẩn thận @ Talha-Talip-açıkgöz, bạn sẽ thực thi Nhiệm vụ khi bạn không mong đợi họ thực thi. Làm điều đó như một đại biểu Func làm cho ý định của bạn rõ ràng.
DalSoft

5

Còn một câu trả lời nữa ... nhưng tôi thường thấy mình trong một trường hợp, khi tôi cần tải dữ liệu đồng thời và đưa nó vào các biến, như:

var cats = new List<Cat>();
var dog = new Dog();

var loadDataTasks = new Task[]
{
    Task.Run(async () => cats = await LoadCatsAsync()),
    Task.Run(async () => dog = await LoadDogAsync())
};

try
{
    await Task.WhenAll(loadDataTasks);
}
catch (Exception ex)
{
    // handle exception
}

1
Nếu LoadCatsAsync()LoadDogAsync()chỉ là các cuộc gọi cơ sở dữ liệu thì chúng bị ràng buộc IO. Task.Run()dành cho công việc gắn với CPU; nó bổ sung thêm chi phí không cần thiết nếu tất cả những gì bạn đang làm đang chờ phản hồi từ máy chủ cơ sở dữ liệu. Câu trả lời được chấp nhận của Yuval là cách phù hợp cho công việc ràng buộc IO.
Stephen Kennedy

@StephenKennedy bạn có thể vui lòng làm rõ loại chi phí nào và nó có thể ảnh hưởng đến hiệu suất bao nhiêu không? Cảm ơn!
Yehor Hromadskyi

Điều đó sẽ khá khó để tóm tắt trong hộp bình luận :) Thay vào đó tôi khuyên bạn nên đọc các bài viết của Stephen Cleary - anh ấy là một chuyên gia về công cụ này. Bắt đầu tại đây: blog.stephencleary.com/2013/10/ khăn
Stephen Kennedy

-1

Tôi đã chuẩn bị một đoạn mã để chỉ cho bạn cách sử dụng tác vụ cho một số tình huống này.

    // method to run tasks in a parallel 
    public async Task RunMultipleTaskParallel(Task[] tasks) {

        await Task.WhenAll(tasks);
    }
    // methode to run task one by one 
    public async Task RunMultipleTaskOneByOne(Task[] tasks)
    {
        for (int i = 0; i < tasks.Length - 1; i++)
            await tasks[i];
    }
    // method to run i task in parallel 
    public async Task RunMultipleTaskParallel(Task[] tasks, int i)
    {
        var countTask = tasks.Length;
        var remainTasks = 0;
        do
        {
            int toTake = (countTask < i) ? countTask : i;
            var limitedTasks = tasks.Skip(remainTasks)
                                    .Take(toTake);
            remainTasks += toTake;
            await RunMultipleTaskParallel(limitedTasks.ToArray());
        } while (remainTasks < countTask);
    }

1
Làm thế nào để có được kết quả của Nhiệm vụ? Ví dụ: để hợp nhất các "hàng" (từ N tác vụ song song) trong một dữ liệu và liên kết nó với Gridview asp.net?
PreguntonCojoneroCabrón
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.