C# - Fundamentals - Task Class

 


Lớp Task đại diện cho một hoạt động đơn lẻ không trả về giá trị và luôn thực thi bất đồng bộ. Những đối tượng Task là một trong những thành phần trọng tâm của task-based asynchronous pattern được giới thiệu đầu tiên ở .NET Framework 4. Bởi vì công việc thể hiện bởi một đối tượng Task thường thực thi bất đồng bộ trên một nhóm luồng thay vì luồng chính của ứng dụng như đồng bộ, bạn có thể sử dụng thuộc tính Status, cũng như IsCanceled, IsCompleted, và IsFaulted, để xác định trạng thái của tác vụ. Phần lớn sử dụng biểu thức lamda để xác định tác vụ được thực hiện.

Để đọc thêm về các kiểu trả về, bạn có thể sử dụng lớp Task<TResult>.

Khởi tạo Task

Ví dụ dưới đây tạo và thực thi 4 task. 3 task thực thi một đại diện Action<T> tên actionmà nó chấp nhận một đối số kiểu Object. Task thứ 4 thực khi theo kiểu biểu thức lambda (một Action) được định nghĩa trong chỗ gọi task tạo phương thức. Mỗi task được tạo và chạy khác nhau:

  • Task t1 được tạo bằng việc gọi một khởi tạo của lớp Task nhưng nó được bắt đầu khi gọi phương thức Start() của nó sau khi task t2 bắt đầu
  • Task t2 được tạo và bắt đầu trong một phương thức đơn lẻ bằng việc gọi phương thức TaskFactory.StartNew(Action<Object>, Object)
  • Task t3 được tạo và bắt đầu trong một phương thức bằng việc gọi phương thức Run(Action)
  • Task t4 được thực thi đồng bộ trong luồng chính bằng việc gọi phương thức Runsynchronously()

Bởi vì task t4 thực thi đồng bộ, nó thực thi trên luồn chính của ứng dụng. Những task còn lại thực thi bất đồng bộ thường là trên một hoặc nhiều thread pool

Tạo và thực thi một task

Các thực thể Task có thể được tạo bằng nhiều cách. Cách tiếp cận phổ biến, có sẵn từ .NET Framework 4.5, là gọi phương thức static Run. Phương thức Run này cung cấp một cách đơn giản để bắt đầu một task sử dụng giá trị mặc định và không cần tham số bắt buộc nào. Ví dụ dưới đây sử dụng phương thức Run(Action) để bắt đầu một task lặp và hiển thị số của con trỏ vòng lặp:


Còn một phương pháp khác, phương thức này bắt đầu một task trong .NET Framework 4, là phương thức static TaskFactory.StartNew. Thuộc tính Task.Factory trả về một đối tượng TaskFactory. Overload của phương thức TaskFactory.StartNew cho phép bạn xác định tham số để truyền vào các lựa chọn tạo task và lên lịch cho task. Ví dụ dưới đây sử dụng phương thức TaskFactory.StartNew để bắt đầu một task. Nó có chức năng y chang ví dụ ở trên vậy đó.


Tách việc tạo task và việc thực thi nó

Lớp Task cũng cung cấp hàm khởi tạo để khởi tạo task nhưng không có lên lịch cho nó hoạt động. Với lý do về hiệu suất, phương thức Task.Run hoặc TaskFactory.StartNew là một kỹ thuật ưu tiên cho việc tạo và lên lịch tính toán cho task, nhưng trong một vài trường hợp khi tạo và thực thi task phải được tách riêng ra, bạn có thể sử dụng hàm khởi tạo và sau đó gọi phương thức Task.Start để lên lịch hoạt động cho task ở một điểm sau đó.

Chờ một hoặc nhiều task hoàn thành

Bởi vì task thường chạy bất đồng bộ trong một threadpool, thread có thể tạo và bắt đầu task tiếp tục ngay sau khi task được khởi tạo. Trong một số trường hợp, khi việc gọi thread trong luồng chính của ứng dụng, ứng dụng có thể kết thúc trước khi task thật sự thực thi. Trong một vài trường hợp khác, logic của ứng dụng của bạn có thể yêu cầu việc tiếp tục thực thi khi một hoặc nhiều task hoàn thành. Bạn có thể đồng bộ hoá việc thực thi của các luồng gọi và các task bất đồng bộ mà nó chạy bằng việc gọi phương thức Wait để chờ một hoặc nhiều phương thức hoàn thành.

Để chờ một task đơn lẻ để hoàn thành, bạn có thê gọi phương thức Task.Wait của nó. Chỗ gọi phương thức Wait block chỗ gọi luồng cho đến khi thực thể lớp đơn lẻ đó hoàn thành thực thi.

Ví dụ dưới đây gọi phương thức Wait() không có tham số để chờ vô điều kiện cho đến khi một task hoàn thành. Task mô phỏng công việc bằng việc gọi phương thức Thread.Sleep để chế độ ngủ trong hai giây


Bạn cũng có thể chờ có điều kiện cho đến khi task hoàn thành. Phương thức Wait(Int32)Wait(TimeSpan) chặn calling thread cho đến khi task xong hoặc một khoảng thời gian trôi qua, cái nào tới trước cũng được. Vì ví dụ dưới đây chạy một task tốn hai giây để ngủ nhưng xác định cho chờ một giây, chỗ gọi thread chặn cho đến khi thời gian hết hạn và trước khi task thực thi xong.


Bạn cũng có thể cung cấp một token huỷ bằng cách gọi phương thức Wait(CancellationToken)Wait(Int32, CancellationToken). Nếu thuộc tính IsCancellationRequested của token là true hoặc trở thành true trong khi phương thức Wait thực thi, phương thức sẽ quăng một OperationCanceledException.

Trong một vài trường hợp, bạn có thể muốn chờ cho task đầu tiên trong chuỗi task đang chạy hoàn thành, nhưng không cần biết đó là task nào. Với mục đích đó, bạn có thể gọi một trong những overload của phương thức Task.WaitAny. Ví dụ tiếp theo tạo ba task, mỗi task sẽ có chế độ ngủ trong thời gian xác định bằng việc tạo ra một con số random. Phương thức WaitAny(Task[]) chờ cái đầu tiên hoàn thành. Sau đó hiển thị thông tin về trạng thái của cả 3 task.


Bạn cũng có thể chờ cho tới khi tất cả các task hoàn thành bằng việc gọi phương thức WaitAll. Ví dụ dưới đây tạo ra 10 task, và chờ tất cả 10 task hoàn thành sau đó hiện trạng thái của chúng.


Chú ý khi bạn chờ một hoặc nhiều task hoàn thành, bất kỳ exception nào quăng ra trong lúc task đang chạy sẽ bị nhân bản trong luồng mà gọi phương thức Wait, ví dụ dưới đây sẽ cho thấy điều đó. Nó chạy 12 task, có 3 task trong số task đó hoàn thành bình thường, và có 3 task trong đóng task đó quăng ra exception. Còn lại 6 task, 3 task bị huỷ trước khi bắt đầu, và 3 cái bị huỷ trong lúc đang thực thi. Ngoại lệ được quăng trong phương thức gọi WaitAll và được xử lý bằng khối try/catch

 

Task và văn hoá

Bắt đầu với ứng dụng desktop có target là .NET Framework 4.6, văn hoá của luồng tạo và dẫn thành một task trở thành một phần của ngữ cảnh của thread. Nghĩa là, bất kể là văn hoá hiện tại của thread có task thực thi như thế nào, văn hoá hiện tại của task là một văn hoá của chỗ gọi task. Với những ứng dụng có target version của .NET Framework trước .NET Framework 4.6, văn hoá của task là văn hoá của luồng mà task đang thực thi. Để biết thêm nhiều thông tin, hãy xem phần "Culture và task-based asynchronous operations" trong chủ đề CultureInfo

Lưu ý: Cửa hàng ứng dụng tuân theo Windows Runtime trong cài đặt và lấy văn hoá mặc định

Đối với lập trình viên debugger

Đối với những lập trình viên viết customer debugger, một vài thành viên nội bộ và cá nhân của task có thể hữu ích (những thứ đó có thể thay đổi từ release thành release). Trường m_taskId phục vụ nhưng một cửa hàng cho thuộc tính Id, tuy nhiên, truy cập vào những trường này trực tiếp từ debugger có thể tiên hơn truy cập từ giá trị trong phương thức getter của thuộc tính (s_taskIdCounter được sử dụng để lấy giá trị ID tiếp theo có sẵn cho task). Tương tự, trường m_stateFlags giữ thông tin về vòng đời hiện tại của task, thông tin cũng có thể được truy cập thông qua thuộc tính Status. Trường m_action lưu một tham chiếu tới đại diện của taskm và trường m_stateObject lưu trạng thái bất đồng bộ được chuyển cho task bởi lập trình viên. Cuối cùng debugger phân tích cú pháp khung ngăn xếp (parse stack frames), phương thức InternalWait phục vụ một đánh dấu tiềm năng khi một task đang tiến hành việc chờ.

Nhận xét

Bài đăng phổ biến