2015-12-19 34 views
12

Ok, về cơ bản tôi có một loạt các nhiệm vụ (10) và tôi muốn bắt đầu tất cả chúng cùng một lúc và đợi chúng hoàn thành. Khi hoàn thành tôi muốn thực hiện các nhiệm vụ khác. Tôi đọc một loạt các nguồn lực về vấn đề này nhưng tôi không thể làm cho nó phù hợp với trường hợp cụ thể của tôi ...Thực hiện các tác vụ song song

Dưới đây là những gì tôi đang có (mã đã được đơn giản hóa):

public async Task RunTasks(){ 
var tasks = new List<Task> 
{ 
    new Task(async() => await DoWork()), 
    //and so on with the other 9 similar tasks 
} 

Parallel.ForEach(tasks, task => 
{ 
    task.Start(); 
}); 

Task.WhenAll(tasks).ContinueWith(done=>{ 
    //Run the other tasks 
}); 
} 

//This function perform some I/O operations 
public async Task DoWork(){ 
    var results = await GetDataFromDatabaseAsync(); 
    foreach(var result in results){ 
     await ReadFromNetwork(result.Url); 
    } 
} 

Vì vậy, vấn đề của tôi là khi tôi chờ đợi các tác vụ hoàn thành với cuộc gọi WhenAll, nó cho tôi biết rằng tất cả các tác vụ đều kết thúc mặc dù không có tác vụ nào được hoàn thành. Tôi đã thử thêm Console.WriteLine vào số foreach và khi tôi đã nhập tác vụ tiếp tục, dữ liệu tiếp tục đến từ Task giây trước đó của tôi chưa thực sự hoàn tất.

Tôi đang làm gì sai ở đây?

+0

Tôi nghi ngờ trong tất cả điều này, bạn có một 'Task ' ở đâu đó (gọi hàm dựng trực tiếp là phần nghi ngờ) và bạn thực sự muốn chờ nhiệm vụ bên trong. Bạn có thể sử dụng 'Task.Run' thay vì khởi tạo Task/Start không? 'Task.Run' sẽ thực hiện phần mở rộng cho bạn. –

+0

Vì vậy, bạn nghĩ mình nên làm: var nhiệm vụ = new List { Task.Run (() => DoWork()) } –

Trả lời

16

Bạn hầu như không bao giờ nên sử dụng trực tiếp phương thức khởi tạo Task. Trong trường hợp của bạn, nhiệm vụ đó chỉ kích hoạt nhiệm vụ thực tế mà bạn không thể chờ đợi.

Bạn chỉ cần gọi DoWork và lấy lại tác vụ, lưu trữ nó trong danh sách và đợi tất cả các tác vụ cần hoàn thành. Ý nghĩa:

tasks.Add(DoWork()); 
// ... 
await Task.WhenAll(tasks); 

Tuy nhiên, phương pháp async chạy đồng bộ cho đến khi chờ đợi đầu tiên trên một nhiệm vụ chưa hoàn thành đạt. Nếu bạn lo lắng về phần đó diễn ra quá lâu sau đó sử dụng Task.Run để giảm tải nó đến một ThreadPool chủ đề và sau đó lưu trữ rằng nhiệm vụ trong danh sách:

tasks.Add(Task.Run(() => DoWork())); 
// ... 
await Task.WhenAll(tasks); 
+0

By * chạy đồng bộ cho đến khi chờ đợi đầu tiên * đã làm bạn muốn có nghĩa là họ sẽ thực sự chỉ chạy async khi chúng ta gọi là 'Task.WhenAll'? Cảm ơn! – Alisson

+0

@Alisson Tôi đã đề cập đến phần đồng bộ của 'DoWork' và không phải là mã gọi nó. – i3arnon

0

Phương pháp DoWork là một I/O phương pháp không đồng bộ. Nó có nghĩa là bạn không cần nhiều luồng để thực hiện một số trong số chúng, vì hầu hết thời gian phương thức sẽ không đồng bộ chờ cho I/O hoàn tất. Một sợi chỉ đủ để làm điều đó.

public async Task RunTasks() 
{ 
    var tasks = new List<Task> 
    { 
     DoWork(), 
     //and so on with the other 9 similar tasks 
    }; 

    await Task.WhenAll(tasks); 

    //Run the other tasks    
} 

Bạn nên gần như never use the Task constructor để tạo tác vụ mới. Để tạo một tác vụ I/O không đồng bộ, chỉ cần gọi phương thức async. Để tạo một tác vụ sẽ được thực hiện trên một luồng thread thread, sử dụng Task.Run. Bạn có thể đọc this article để có giải thích chi tiết về Task.Run và các tùy chọn tạo nhiệm vụ khác.

0

Nếu bạn muốn chạy song song những của nhiệm vụ trong chủ đề khác nhau sử dụng TPL bạn có thể cần một cái gì đó như thế này:

public async Task RunTasks() 
{ 
    var tasks = new List<Func<Task>> 
    { 
     DoWork, 
     //... 
    }; 

    await Task.WhenAll(tasks.AsParallel().Select(async task => await task())); 

    //Run the other tasks 
} 

Những cách tiếp cận parallelizing chỉ số lượng nhỏ mã: các hàng đợi của phương pháp để các hồ bơi thread và sự trở lại của một chưa hoàn thành Task. Ngoài ra đối với số lượng nhỏ công việc song song có thể mất nhiều thời gian hơn là chỉ chạy không đồng bộ. Điều này có thể có ý nghĩa chỉ khi nhiệm vụ của bạn làm việc lâu hơn (đồng bộ) trước khi chờ đợi đầu tiên.

Đối với hầu hết các trường hợp cách tốt hơn sẽ là:

public async Task RunTasks() 
{ 
    await Task.WhenAll(new [] 
    { 
     DoWork(), 
     //... 
    }); 
    //Run the other tasks 
} 

Để ý kiến ​​của tôi trong mã của bạn:

  1. Bạn không nên quấn mã của bạn trong Task trước khi đi qua để Parallel.ForEach.

  2. Bạn chỉ có thể awaitTask.WhenAll thay vì sử dụng ContinueWith.

5

Thực chất bạn đang trộn hai mô hình không đồng bộ không tương thích; tức là Parallel.ForEach()async-await.

Cho những gì bạn muốn, hãy thực hiện việc này hay cách khác. Ví dụ. bạn chỉ có thể sử dụng Parallel.For[Each]() và bỏ toàn bộ sự chờ đợi không đồng bộ. Parallel.For[Each]() sẽ chỉ trở lại khi tất cả các tác vụ song song được hoàn thành và sau đó bạn có thể chuyển sang các tác vụ khác.

Mã này có một số vấn đề khác nữa:

  • bạn đánh dấu các phương pháp async nhưng đừng chờ đợi ở nó (đang chờ đón bạn làm có là trong các đại biểu, không phải là phương pháp);

  • bạn gần như chắc chắn muốn .ConfigureAwait(false) khi chờ đợi, đặc biệt nếu bạn không cố gắng sử dụng kết quả ngay lập tức trong chuỗi giao diện người dùng.

Các vấn đề liên quan