Thứ Năm, 28 tháng 6, 2012

Sử dụng Parallel.Invoke() của C#


Trong phạm vi bài viết này, chúng ta sẽ học cách sử dụng phương thức Parallel.Invoke() để có thể thực hiện song song các tác vụ trong chương trình thay vì thực hiện chúng một cách tuần tự.

.NET Framework 4.0 đã giới thiệu lớp Parallel giúp cho việc thực hiện các tác vụ song song dễ dàng hơn trong việc cài đặt. Và có nhiều tình huống, việc thực hiện các tác vụ song song sẽ giúp cho chúng ta sử dụng CPU hợp lý hơn, ví dụ, bạn cần có 3 tác vụ cần phải được thực thi, gồm có:
1.       Copy file A từ ổ đĩa X sang ổ đĩa Y
2.       Nén file ảnh B và lưu thành file ảnh C
3.       Mở tập tin PDF D
Nếu lập trình theo dạng tuần tự theo thứ tự như trên, bạn sẽ đợi xong hai việc đầu thì bạn mới có thể mở tập tin PDF để đọc. Giả sử file A cần copy có dung lượng 4GB, và ảnh B có độ phân giải lên đến 16 Mega Pixel, và do đó bạn phải đợi vài phút để đọc được tập tin D, như vậy thật không đúng. Bạn sẽ nghĩ đến chuyện làm sao để ba việc được thực hiện song song, việc nào xong trước cũng tốt cả. Và trong phạm vi bài viết này, chúng ta sẽ học cách sử dụng phương thức Parallel.Invoke() để có thể thực hiện song song các tác vụ trong chương trình.
Trong quá trình thực thi chương trình các tác vụ sẽ được sắp xếp vào đường ống gọi là ThreadPool để chờ thực thi. Có nhiều thuật toán để cải thiện cách thực thi của ThreadPool, và một cải thiện giúp cho chúng ta có thể thực thi song song là cho phép nhiều tác vụ cùng được thực hiện một lần.
Để thực hiện nhiều tác vụ song song chúng ta thực hiện cú pháp
Parallel.Invoke(()=>DoTask1(),()=>DoTask2(), .., ()=>DoTaskn())
Các tác vụ DoTask1 đến DoTaskn sẽ được thực hiện song song.
Để triệu gọi được lớp Parallel, chúng ta cần khai báo sử dụng namespace System.Threading.Tasks.
Có một lầm lẫn mà chúng ta thương mắc phải thì thực hiện việc lập trình song song là chúng ta cho rằng lập trình song song sẽ cho tổng thời gian thực thi nhanh hơn so với thực thi các tác vụ một cách tuần tự, trên thực tế, câu chuyện không hoàn toàn đúng như chúng ta nghĩ. Trong nhiều trường hợp thực hiện song song làm tổng thời gian thực thi diễn ra lâu hơn. Nhưng các bạn đừng quên ưu điểm của lập trình song song như tôi đã nêu ở đầu bài nhé.
Ở đây chúng ta sẽ thực hiện một ví dụ đơn giản, đó là tạo một ứng dụng Console có nhiệm vụ tính toán và cho ra kết quả của các phép toán dưới đây:
1+2+… +n
1*2*…*n
Chúng ta sẽ cài đặt ứng dụng để học cách lập trình với Parallel.Invoke() và đồng thời chứng minh nhận định “Sử dụng Parallel.Invoke() lúc nào cũng nhanh hơn tính toán tuần tự là sai”.
Đầu tiên chúng ta cài đặt một lớp Math có chức năng tính hai bài toán chúng ta vừa nêu:


public class Math
    {
        public static int Sum(int n)
        {
            var result = n * (n + 1) / 2;
            return  result;
 
        }
 
        public static int Mul(int n)
        {
            var result = 1;
            for (var i = 1; i <= n; i++)
            {
                result *= i;
            }
 
            return result;
        }
    }
 
 Tiếp theo chúng ta sẽ cài đặt phương thức tính toán theo kiểu tuần tự NormalCalculate, trong đó chúng ta sử dụng lớp StopWatch để tính toán thời gian thực thi:


private static void NormalCaculate(int n)
        {
            Console.WriteLine("TINH THEO CACH THONG THUONG");
            var sw = Stopwatch.StartNew();
            Console.WriteLine(@"Bat dau tinh tong!");
            Console.WriteLine("Tong bang {0}", Math.Sum(n));
            Console.WriteLine("Bat dau tinh n!");
            Console.WriteLine("Giai thua {0} bang {1}", n, Math.Mul(n));
            sw.Stop();
            Console.WriteLine("Thoi gian thuc hien theo cach thong thuong {0}", sw.Elapsed);
            sw.Reset(); 
        }
 
 Và chúng ta cài đặt cho phương thức thực thi tính toán sử dụng Parallel.Invoke()


private static void ParallelCaculate(int n)
        {
            var sw = Stopwatch.StartNew();
            Console.WriteLine("SU DUNG Parallel.Invoke");
            Parallel.Invoke(
                () =>
                {
                    Console.WriteLine(@"Bat dau tinh tong!");
                    Console.WriteLine("Tong bang {0}", Math.Sum(n));
                },
 
                () =>
                {
                    Console.WriteLine("Bat dau tinh n!");
                    Console.WriteLine("Giai thua {0} bang {1}", n, Math.Mul(n));
                }
                );
            sw.Stop();
            Console.WriteLine("Thoi gian thuc hien theo cach su dung Parallel {0}", sw.Elapsed);
            sw.Reset();
        }
 
 Sau đó chúng ta triệu gọi hai phương thức này để xem kết quả:
Với N=10 chúng ta có kết quả:

Chúng ta có thể thấy rằng, sử dụng Parallel.Invoke sẽ giúp hai tác vụ được thực thi song song, nhưng rõ là tính tuần tự có tổng thời gian thực hiện thấp hơn so với cách thực thi song song. Và với N =15 thì kết quả cũng tương tự:

Đến đây hy vọng các bạn đã quen với Parallel.Invoke và sử dụng chúng đúng thời điểm.

Không có nhận xét nào: