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:
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 ...async
- hãy xem kỹ phần async - await ở phần trước) thì kết quả lưu vào result
với result.IsCompleted
là true
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.
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(); }
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
Nguồn tin: XuanThuLab