Lập trình đa luồng với Task

Bài viết được đăng trên Jou Lập trình
Trong phiên bản .NET framework 4.0, Microsoft đã bổ sung nhiều thư viện hỗ trợ việc xử lý đa luồng (multi-threading), nhằm đơn giản hóa việc lập trình lẫn hiệu suất của chương trình. Trong bài viết này, tôi xin hướng dẫn các bạn sử dụng lớp System.Threading.Task.

Task

Một Task đại diện cho một số công việc cần phải hoàn thành. Các công việc này có thể chạy trong 1 thread riêng hoặc có thể thực thi một cách đồng bộ, và kết quả được chờ đợi trả về ở thread gọi nó.
Task là một lớp trừu tượng, bạn có nhiều kiểm soát hơn là việc sử dụng Thread.
Task cho phép bạn tổ chức công việc một cách linh hoạt. Ví dụ:
·         Định nghĩa các công việc tiếp theo, được làm sau khi 1 task khác kết thúc. Công việc có thể khác khi công việc trước đó thực thi thành công hay thất bại.
·         Nhận được kết quả trả từ một luồng khác.
·         Kiểm soát lỗi tốt hơn.
·         Dễ dàng chờ đợi 1 hay nhiều Task thực thi xong.

Tạo một Task (công việc)

Để tạo 1 Task mà không cần nhận lại kết quả:
Cách 1:
Task.Factory.StartNew(() => DoSomeWork); 
Cách 2:
var action = new Action(DoSomeWork);
var task = new Task(action);
task.Start();

Thực hiện Task liên tiếp

Với Task, bạn có thể chỉ ra 1 công việc thực hiện sau khi một công việc khác hoàn thành (tương tự như Background Worker)

        static void Example2()
        {
            var t1 = new Task(DoOnFirst);
            var t2 = t1.ContinueWith(DoOnSecond);
            t1.Start();
        }
        static void DoOnFirst()
        {
            Console.WriteLine("doing some task {0}", Task.CurrentId);
            Thread.Sleep(3000);
        }
        static void DoOnSecond(Task t)
        {
            Console.WriteLine("task {0} finished", t.Id);
            Console.WriteLine("this task id {0}", Task.CurrentId);
            Console.WriteLine("do some cleanup");
            Thread.Sleep(3000);
        }
Với TaskContinuationOptions, bạn chỉ ra điều kiện để thực hiện công việc tiếp theo khi công việc trước đó hoàn thành. Trong ví dụ dưới đây, Task 3 sẽ thực hiện trong trường hợp Task 2 bị lỗi (throw new exception)
        static void Example3()
        {
            var t1 = new Task(DoOnFirst);
            var t2 = t1.ContinueWith(DoOnSecond);
            var t3 = t2.ContinueWith(DoOnThird, TaskContinuationOptions.OnlyOnFaulted);
            t1.Start();
        }
        static void DoOnSecond(Task t)
        {
            Console.WriteLine("task {0} finished", t.Id);
            Console.WriteLine("this task id {0}", Task.CurrentId);
            Console.WriteLine("do some cleanup");
            Thread.Sleep(3000);
            throw new Exception("Failed");
        }
        static  void DoOnThird(Task t)
        {
            Console.WriteLine("task {0} failed", t.Id);
            Console.WriteLine("this task id {0}", Task.CurrentId);
        }
 
Bạn có thể tham khảo thêm TaskContinuationOptions ở trang MSDN

Nhận kết quả trả về từ Task

Để nhận được kết quả trả về từ Task, bạn sử dụng class Task<T> với T là kiểu dữ liệu của kết quả trả về.
Dưới đây là 3 cách gọi hàm:
        static void Example41()
        {
            var task = Task.Factory.StartNew(delegate
            {
                DoSomeWork();
                return 2712;
            });
            Console.WriteLine(task.Result);
        }

        static void Example42()
        {
            var task = Task.Factory.StartNew((Func<int>)DoSomeWork1);
            Console.WriteLine(task.Result);
        }

        static void Example43()
        {
            var task = Task<int>.Factory.StartNew(DoSomeWork1);
            Console.WriteLine(task.Result);
        }

        static int DoSomeWork1()
        {
            DoSomeWork();
            return 2712;
        }
 Lưu ý: TResult property sẽ khóa luồng gọi Task cho đến khi Task đó thực hiện xong.

Chờ đợi 1 (hoặc nhiều) Task

Để chờ đợi 1 Task hoàn thành, bạn gọi hàm Task.Wait
        static void Example51()
        {
            var task = Task.Factory.StartNew(()=>
            {
                DoSomeWork();
                Console.WriteLine("Complete");
            });
            task.Wait();
        }
Để chờ đợi nhiều Task thực thi xong, bạn gọi hàm Task.WaitAll(Task[]).
        static void Example52()
        {
            var task1 = Task.Factory.StartNew(DoSomeWork);

            var task2 = Task.Factory.StartNew(DoSomeWork);

            Task.WaitAll(task1, task2);
        }
Download ví dụ ở MediaFire

 Kết luận:

Hi vọng với bài viết nhỏ này, bạn nắm được các cách cơ bản để thực thi 1 công việc đa luồng và kiểm soát chúng tốt hơn.
Trong phần tới, tôi sẽ hướng dẫn bạn viết 1 ví dụ thực tế về cách sử dụng Task trong lập trình.
Án Bình Trọng