- Thi hành tác vụ nhiều lần với Parallel.For
- Parallel.For với Action async
- Thi hành tác vụ song song với Parallel.ForEach
- Thi hành nhiều tác vụ song song với Parallel.Invoke
Thi hành song song tác vụ với Parallel.For
Lớp Parallel thuộc namespace System.Threading.Tasks
, nó trừu
tượng hóa các thread
, lớp này có phương thức tĩnh Parallel.For
, Parallel.ForEach
để thực hiện vòng lặp for
và foreach
để chạy song song các tác vụ.
Nếu với for
và foreach
trong C#, thì vòng lặp đó chạy trong một thread, nhưng với
Parallel
nó sử dụng đa tác vụ, đa tiến trình để thực hiện nội dung lặp.
Ngoài ra là Parallel.Invoke
để thực hiện một Action
có khả năng chạy song song.
Parallel.For
có nhiều quá tải, cú pháp bản đơn giản như sau:
ParallelLoopResult result = Parallel.For(i1, i2, task);
Vòng lặp chạy (biến chạy) từ số nguyên i1
đến i2
, mỗi lần lặp nó sẽ thực hiện
Action task
task
là một delegate, kiểu Action<int>
có nghĩa nó làm phương thức trả
về void, có một tham số kiểu int, tham số này là biến chạy. Ví dụ đây là một action phù hợp cho
Parallel.For
Action<int> action = (int x) => { // Doing somthing here ... };
result
đối tượng lớp ParallelLoopResult
trả về từ Paralell.For
,
thuộc tính ParallelLoopResult.IsCompleted
cho biết vòng lặp đã được duyệt qua hết,
tất cả các task đã khởi chạy.
Ví dụ: Source Code
class Program { //In thông tin, Task ID và thread ID đang chạy public static void PintInfo(string info) => Console.WriteLine($"{info, 10} task:{Task.CurrentId,3} " + $"thread: {Thread.CurrentThread.ManagedThreadId}"); // Phương thức phù hợp với Action<int>, được làm tham số action của Parallel.For public static void RunTask(int i) { PintInfo($"Start {i,3}"); Task.Delay(1000).Wait(); // Task dừng 1s - rồi mới chạy tiếp PintInfo($"Finish {i,3}"); } public static void ParallelFor() { ParallelLoopResult result = Parallel.For(1, 20, RunTask); // Vòng lặp tạo ra 20 lần chạy RunTask Console.WriteLine($"All task start and finish: {result.IsCompleted}"); } static void Main(string[] args) { ParallelFor(); Console.WriteLine("Press any key ..."); Console.ReadKey(); } }
Từ kết quả trên bạn thấy:
- Lệnh
Parallel.For
khởi chạy song song nhiều tác vụ (thời điểm bắt đầu của mỗi tác vụ không giống nhau, có những tác vụ đã kết thúc thì tác vụ sau mới chạy, nó có thể phụ thuộc vào tài nguyên hệ thống RAM, CPU ... - Một task nó có chạy trên một thread nào đó (chứ không phải mỗi task một thread), một thread có thể sử dụng bởi nhiều task. Ví dụ nhìn vào kết quả, task với i = 1, và task với id = 15 chạy trên một thread
- Khi tất vòng lặp duyệt qua hết, có nghĩa là đã khởi chạy hết các tác vụ,
nếu tất cả các tác vụ trả về (khi chạy xong hàm Action, hoặc ngay khi gọi Action nếu
Action là
async
- hãy xem kỹ phần async - await ở phần trước) thì kết quả lưu vàoresult
vớiresult.IsCompleted
làtrue
Parallel.For khi Action là async
Để ý, bản thân vòng lặp Parallel.For
, khi các Action chạy, mặc dù
chúng chạy trên những Task và Thread, nhưng khi tất cả các Action hoàn hành thì
vòng lặp mới hoàn thành. Ở ví dụ trên, bạn thấy tất cả đều Finish mới
trả về kết quả result
(All task start and finish: True)
Điều này, lại dẫn đến Parallel.For
khóa(block) thread gọi nó.
Để không bị khóa, có thể chuyển các Action là async
public static async void RunTask(int i) { PintInfo($"Start {i,3}"); // Task.Delay(1000).Wait(); // Task dừng 1s - rồi mới chạy tiếp await Task.Delay(1); // Task.Delay là một async nên có thể await, RunTask chuyển điểm gọi nó tại đây PintInfo($"Finish {i,3}"); }
Kết quả khi chạy, vòng lặp Parallet.For
nó trả về ngay khi tất cả
các Task đã khởi chạy - (chứ không cần tất cả các task chạy và kết thúc
như trường hợp trước).
Khi Parallet.For
hoàn thành, có một số Task đã kết thúc có những Task
vẫn đang chạy.
Parallel.ForEach chạy song song tác vụ
Với Parallet.ForEach
cũng là vòng lặp để chạy nhiều tác vụ,
nhưng nó duyệt qua các Collection như Mảng, List ... tương tự như vòng lặp foreach
. Cú pháp cơ bản như sau:
ParallelLoopResult result = Parallel.ForEach(source, RunTask);
Trong đó source
là một Collection như mảng, List. RunTask
là Action
, có 1 tham số có kiểu giống kiểu phần tử trong source
, giá
trị tham số này là giá trị phần tử trong source
trong mỗi vòng lặp. Ví dụ:
public static async void RunTask(string s) { PintInfo($"Start {s,10}"); await Task.Delay(1); // Task.Delay là một async nên có thể await, RunTask chuyển điểm gọi nó tại đây PintInfo($"Finish {s,10}"); } public static void ParallelFor() { string[] source = new string[] {"xuanthulab1","xuanthulab2","xuanthulab3", "xuanthulab4","xuanthulab5","xuanthulab6", "xuanthulab7","xuanthulab8","xuanthulab9"}; // Dùng List thì khởi tạo // List<string> source = new List<string>(); // source.Add("xuanthulab1"); ParallelLoopResult result = Parallel.ForEach( source, RunTask ); Console.WriteLine($"All task started: {result.IsCompleted}"); } static void Main(string[] args) { ParallelFor(); Console.WriteLine("Press any key ..."); Console.ReadKey(); }
Parallel.Invoke chạy song song nhiều loại tác vụ (phương thức)
Với các vòng lặp ở trên, thì các tác vụ định nghĩa trọng một Action, nhưng nếu muốn chạy
song song nhiều loại Action (phương thức) một lúc thì dùng Paralell.Invoke
Parallel.Invoke(action1, action2, action3);
Trong đó tham số là các Action
public static void PintInfo(string info) => Console.WriteLine($"{info, 10} task:{Task.CurrentId,3} " + $"thread: {Thread.CurrentThread.ManagedThreadId}"); public static async void RunTask(string s) { PintInfo($"Start {s,10}"); await Task.Delay(1); PintInfo($"Finish {s,10}"); } public static void actionA() { PintInfo($"Finish {"ActionA",10}"); } public static void actionB() { PintInfo($"Finish {"ActionB",10}"); } public static void ParallelInvoke() { Action action1 = () => { RunTask("Action1"); }; Parallel.Invoke(action1, actionA, actionB); } static void Main(string[] args) { ParallelInvoke(); Console.WriteLine("Press any key ..."); Console.ReadKey(); }
Tham khảo các mã nguồn CS028_Parallelhoặc tải về ex028