CodeGym /Các khóa học /C# SELF /Hủy các thao tác bất đồng bộ

Hủy các thao tác bất đồng bộ

C# SELF
Mức độ , Bài học
Có sẵn

1. Giới thiệu

Khi bạn khởi chạy một thao tác bất đồng bộ (hoặc dài), người dùng (hoặc mã khác) có thể đột ngột quyết định: "Khoan! Không cần nữa! Dừng lại!". Ví dụ, người dùng quyết định hủy tải một file khổng lồ, đóng cửa sổ chương trình hoặc thay đổi ý định khi tìm thông tin trong cơ sở dữ liệu lớn. Nếu không có hỗ trợ hủy, chương trình của bạn có thể tiếp tục chạy và ngốn tài nguyên vô ích — không phải cách tốt để đối xử với người dùng và máy tính.

Kịch bản hủy điển hình:

  • Hủy tải file hoặc hủy gửi dữ liệu.
  • Thoát nhanh khỏi xử lý dữ liệu phức tạp theo yêu cầu người dùng.
  • Khi tạm dừng/đình chỉ đột ngột một tác vụ dài mà nó không còn phù hợp nữa.

Hủy — là thành phần bí mật giúp ứng dụng thân thiện, phản hồi tốt và tiết kiệm.

Cách hủy các thao tác bất đồng bộ trong .NET?

Trong .NET để hủy các tác vụ dài dùng khái niệm "token hủy" (CancellationToken). Đây là một đối tượng đặc biệt được truyền qua các thành phần của tác vụ. Nếu ai đó yêu cầu hủy — token sẽ báo ngay cho tất cả phần quan tâm trong chương trình. Thực tế nó giống lá cờ đỏ: ai thấy trước — sẽ dừng trước.

Trong .NET cơ chế này được hiện thực bằng hai lớp chính:

Quan trọng: chính token hủy không tự động dừng việc thực thi mã, nó chỉ "phát tín hiệu" — ứng dụng của bạn quyết định khi nào và như thế nào phản ứng với tín hiệu đó.

2. Tạo token hủy và hủy tác vụ

Hãy xem cách hoạt động bằng ví dụ đơn giản (chúng ta sẽ phát triển ứng dụng console học tập).

Ví dụ: thao tác bất đồng bộ đơn giản có hỗ trợ hủy


using System;
using System.Threading;
using System.Threading.Tasks;

namespace DemoApp
{
    class Program
    {
        static async Task Main()
        {
            // Tạo nguồn token hủy
            CancellationTokenSource cts = new CancellationTokenSource();

            // Khởi chạy tác vụ bất đồng bộ
            Task longRunningTask = DoWorkAsync(cts.Token);

            Console.WriteLine("Nhấn phím bất kỳ để hủy thao tác...");
            Console.ReadKey();

            // Yêu cầu hủy
            cts.Cancel();

            try
            {
                await longRunningTask;
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine("Thao tác đã bị hủy!");
            }
        }

        // Phương thức bất đồng bộ hỗ trợ hủy
        static async Task DoWorkAsync(CancellationToken cancellationToken)
        {
            for (int i = 0; i < 10; i++)
            {
                // Kiểm tra tín hiệu hủy
                cancellationToken.ThrowIfCancellationRequested();

                Console.WriteLine($"Thực hiện bước {i + 1}/10...");
                await Task.Delay(1000); // Delay 1 giây
            }

            Console.WriteLine("Thao tác hoàn thành thành công!");
        }
    }
}

Cách nó hoạt động?

- Chúng ta tạo CancellationTokenSource (cts), từ đó lấy token (cts.Token).
- Truyền token này vào thao tác bất đồng bộ.
- Bên trong DoWorkAsync() thường xuyên kiểm tra token bằng phương thức ThrowIfCancellationRequested(). Nếu người dùng yêu cầu hủy — phương thức sẽ ném ngoại lệ OperationCanceledException, và tác vụ sẽ dừng lại.
- Ở Main() chờ nhấn phím và gọi cts.Cancel() để "báo hiệu" cần dừng thao tác.

Nếu bạn không kiểm tra cancellationToken.IsCancellationRequested hoặc không gọi ThrowIfCancellationRequested(), tác vụ sẽ tiếp tục chạy như chưa có gì — token chỉ là lá cờ thông tin.

3. CancellationToken: nó hoạt động thế nào? Và một chút "ma thuật"

Token hủy — là một đối tượng dễ dàng truyền giữa các phương thức và tác vụ. Điều này cho phép linh hoạt lớn:

  • Cùng một token có thể dùng trong nhiều thao tác bất đồng bộ và đồng bộ.
  • Có thể tổ chức "hủy nhóm" cho tất cả tác vụ nếu nhiều thao tác sử dụng token từ cùng một CancellationTokenSource.
  • Token hủy không ép buộc: ngay cả khi bạn bỏ qua nó, mã vẫn chạy như trước.

Quản lý hủy: kiểm tra token ở đâu và như thế nào?

Kiểm tra xem "lá cờ hủy" có được bật không nên làm ở những chỗ hợp lý: trong vòng lặp, mỗi bước xử lý dài, khi chuyển giữa các giai đoạn, v.v.


// Ở bất kỳ điểm nào cần kiểm tra
if (cancellationToken.IsCancellationRequested)
{
    Console.WriteLine("Thao tác bị hủy! Thoát...");
    return;
}

// Hoặc ngắn gọn và ném ngoại lệ
cancellationToken.ThrowIfCancellationRequested();

Thường dùng ThrowIfCancellationRequested() — nó ném ngoại lệ đặc biệt, có thể bị bắt ở mã gọi.

4. Phương thức bất đồng bộ của thư viện chuẩn

Rất nhiều lớp và phương thức trong .NET (đặc biệt là các phương thức bất đồng bộ) hỗ trợ CancellationToken "mặc định". Bạn nên dùng chúng để dừng thao tác một cách "đúng".

Ví dụ đọc file bất đồng bộ:


using System.IO;
using System.Threading;
using System.Threading.Tasks;

class FileDemo
{
    public static async Task ReadFileWithCancelAsync(string filePath, CancellationToken cancellationToken)
    {
        using FileStream stream = File.OpenRead(filePath);
        byte[] buffer = new byte[4096];

        int bytesRead;
        while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken)) > 0)
        {
            // Xử lý dữ liệu...
            // Nếu token hủy được yêu cầu, ReadAsync sẽ tự ném OperationCanceledException
        }
    }
}

Chi tiết về FileStream.ReadAsyncCancellationToken xem trong tài liệu chính thức.

5. Những điểm hữu ích cần biết

Timeout — cũng là hủy!

Bạn có thể tự động hủy thao tác sau khi quá thời gian cho phép. Để làm điều đó, token hủy có thể "lập trình":


// Tạo CancellationTokenSource với timeout (ví dụ, 5 giây)
CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));

Sau 5 giây token sẽ tự động "được bật", và tất cả thao tác dùng token đó sẽ dừng ở lần kiểm tra tiếp theo. Rất tiện khi bạn không muốn chờ mãi.

Chuyện gì xảy ra khi hủy?

Khi bạn gọi Cancel() trên nguồn token, mọi phương thức dùng token đó và kiểm tra trạng thái sẽ biết về thay đổi. Nhưng nếu code của bạn không kiểm tra token — thì hủy sẽ không xảy ra.

Lỗi phổ biến: quên truyền token vào mọi thao tác bất đồng bộ và dài. Khi đó một phần sẽ bị hủy, phần khác sẽ tiếp tục chạy như không có gì.

Minh họa: quy trình hủy diễn ra thế nào

sequenceDiagram
    participant Main as Luồng chính
    participant CTS as CancellationTokenSource
    participant Task as Tác vụ bất đồng bộ

    Main->>CTS: tạo CTS, lấy token
    Main->>Task: truyền token vào tác vụ bất đồng bộ
    Note over Task: Tác vụ định kỳ kiểm tra token
    Main->>CTS: gọi Cancel()
    CTS-->>Task: token nhận trạng thái "đã hủy"
    Task-->>Main: ném OperationCanceledException
    Main->>Main: bắt ngoại lệ và kết thúc công việc

Hủy được dùng ở đâu?

  • Tải bất đồng bộ và request tới server: có thể hủy khi mạng kém hoặc người dùng chán.
  • Tính toán lớn: có thể dừng theo timeout hoặc theo yêu cầu người dùng.
  • Hoạt động mạng, làm việc với file, xử lý tập hợp lớn chạy nền.

Đến đây bạn đã làm quen với hủy các thao tác bất đồng bộ — giờ ứng dụng của bạn sẽ không chỉ nhanh mà còn biết "quan tâm"!

6. Lời khuyên và lỗi thường gặp

Đừng quên truyền token hủy vào mọi phương thức và lệnh hỗ trợ cancellation. Nếu bạn bỏ sót ở đâu đó — thao tác có thể "treo" và không dừng.

Kiểm tra token thường xuyên — nhất là trong các vòng lặp dài, xử lý file hoặc tải lượng lớn. Dùng IsCancellationRequested hoặc ThrowIfCancellationRequested().

Đừng cố gắng "ép buộc" kết thúc thread hoặc task từ bên ngoài: token hủy là lời yêu cầu dừng lại, chứ không phải cái búa đập vào đầu thread.

Các hàm thư viện chuẩn như ReadAsync, Delay, HttpClient.SendAsync và nhiều cái khác đã hỗ trợ hủy qua token. Hãy dùng chúng!

Khi xử lý hủy, hãy bắt đúng OperationCanceledException — đó là ngoại lệ đặc biệt thông báo việc hủy hợp lệ theo yêu cầu.

2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Hủy tác vụ đơn giản bằng CancellationToken
Hủy tác vụ đơn giản bằng CancellationToken
1
Khảo sát/đố vui
, cấp độ , bài học
Không có sẵn
Các thao tác bất đồng bộ với file
Ưu điểm của việc làm việc bất đồng bộ với file
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION