CodeGym /Các khóa học /C# SELF /So sánh Task

So sánh TaskThread

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

1. Введение

Giờ là lúc tìm hiểu — khác nhau cơ bản giữa TaskThread là gì? Sao C# nhiều năm nay khuyến nghị dùng Task thay vì quản lý thread trực tiếp? Trong trường hợp nào bạn vẫn có thể dùng thread thủ công, và khi nào thì sống theo phong cách tasks là đủ (và cần thiết)?

Nếu bạn cảm thấy mấy từ "потоки" và "задачи" đang hơi lẫn lộn đâu đó trong đầu và tim đập nhanh — đừng lo, bạn không đơn độc. Ngay cả dev có kinh nghiệm đôi khi cũng bối rối khi nhắc tới parallelism và asynchrony.

Hãy sắp xếp mọi thứ cho rõ ràng. Đi nào!

Краткая история появления Task

Ngày xưa (trước .NET 4.0) cách hiển nhiên duy nhất để chạy code song song hay "nền" là tạo thread mới. Ví dụ, new Thread(() => { ... }).Start(); Thread dễ hiểu vì đơn giản. Nhưng chúng tệ ở chỗ mọi thứ đều trên vai bạn. Cấp phát tài nguyên, lifecycle, xử lý ngoại lệ, đồng bộ hóa, monitoring, scalability — tất cả là việc dev phải lo. Trong lập trình thì đôi khi mình muốn lười hơn một chút!

Mọi thứ thay đổi với sự xuất hiện của tasks — Task — từ namespace System.Threading.Tasks.Task. Task không phải là thread. Đó là một abstraction linh hoạt hơn, mô tả công việc sẽ được thực hiện trong tương lai, có thể song song.

2. Thread — "Голый поток"

Thread — là đơn vị thực thi cấp thấp, đại diện cho một phần tài nguyên hệ điều hành được cấp phát (stack riêng, execution context, v.v.). Nếu bạn tạo thread thủ công, bạn chịu trách nhiệm start, finish và mọi khía cạnh của vòng đời nó.

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        Thread thread = new Thread(() => {
            Console.WriteLine("Hello from thread!");
        });

        thread.Start();
        thread.Join(); // Chờ thread kết thúc
    }
}
  • Ở đây chúng ta tạo thread, nó thực thi lambda trên stack của nó.
  • Sau khi start thread ta gọi Join() để chờ nó xong.

В чём подвох?

  • Mỗi thread tiêu tốn bộ nhớ (stack, ~1 MB).
  • Trong .NET không nên tạo hàng nghìn thread thủ công — hệ thống sẽ khổ sở.
  • Nếu quên gọi Join(), main thread có thể kết thúc sớm hơn thread con và chương trình "bị cắt ngang".
  • Exception bên trong thread sẽ không tự nhảy ra ngoài — phải catch riêng!
  • Nếu đã start thread — không có cách "đẹp" để cancel nó (không có Stop()!).

3. Task — "Задачи нового поколения"

Task — abstraction thông minh hơn, đại diện cho "công việc sẽ được làm vào lúc nào đó". Dưới nắp, tasks chạy trên thread pools ThreadPool, hiệu quả hơn nhiều so với việc tạo ra quá nhiều thread. Bạn không quản lý việc tạo chúng thủ công — pool lo việc đó, scale số lượng thread tùy tải.

using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        Task task = Task.Run(() =>
        {
            Console.WriteLine("Hello from Task!");
        });

        await task; // Đợi task hoàn thành
    }
}
  • Task ở đây không đảm bảo chạy trên một thread riêng, nhưng thường chạy trên thread từ pool.
  • Bạn có thể chờ task bằng cách quen thuộc (await trong method async hoặc task.Wait() trong sync).

4. В чем разница между Task и Thread?

Hãy sắp xếp rõ ràng khác nhau của chúng, dùng cho mục đích gì và những cave (khó thấy) là gì.

Thread Task
Абстракция Поток ОС Công việc/Task (abstraction, có thể dùng thread)
Запуск Через new Thread(...).Start() Через Task.Run(...), Task.Factory.StartNew(...), async-методы
Прямое управление Да (старт, Join, приоритет и т.д.) Нет, управление берёт на себя .NET
Пул потоков Нет, поток создаётся всегда новый Да, чаще всего использует ThreadPool
Управление ресурсами Выделяется собственный стек Ресурсы переиспользуются пулом
Масштабируемость Плохо: неэффективно для 1000+ потоков Отлично: тысячи задач = здорово
Взаимодействие Отдельный поток с точки зрения ОС Может быть продолжением текущего потока, может быть на ThreadPool
Исключения Требует явного перехвата, иначе могут "пропасть" Исключения сохраняются в Task; можно поймать при await или .Wait()
Отмена Нет стандартного способа Да, поддержка через CancellationToken
Итоги работы Дождаться через Join() await, .Wait(), .Result
Использовать для Спец. случаев — потоки UI, long-lived потоки Почти всех фоновых/параллельных задач

5. Когда что использовать?

Когда использовать Thread?

Thành thật mà nói, trong mã .NET hiện đại, tự tạo thread rất hiếm khi cần. Ví dụ hợp lý:

  • Cần tạo thread chạy cực lâu (ví dụ: truyền tín hiệu radio liên tục, hoặc xử lý dữ liệu từ hardware), và nó "đặc biệt": priority thấp, culture riêng, tên riêng.
  • Đôi khi để tích hợp với low-level API yêu cầu quản lý thread thủ công.
  • Trong các trường hợp rất đặc thù, như custom task schedulers.

Trong hầu hết các trường hợp còn lại — Task là lựa chọn đúng và hiện đại hơn.

Когда использовать Task?

Hầu như luôn khi bạn cần chạy công việc "nền" hoặc "song song":

  • Bất kỳ tính toán nền nào phù hợp chạy trên thread pool (ví dụ: xử lý request server, parse file, gửi email hàng loạt).
  • Chạy các thao tác bất đồng bộ (async/await) — API trả về Task hoặc Task<T>.
  • Kết hợp tasks, xử lý continuations, làm việc với chuỗi các tác vụ.
  • Dễ cancel, chờ và thu kết quả: Task hỗ trợ CancellationToken, dễ tích hợp với API hiện đại.
  • Thao tác I/O bất đồng bộ: network requests, file I/O, DB.

Сравнение

Сценарий Thread Task
Long-lived поток (например, свой сервис) Да Нет
Массовое выполнение коротких задач Нет Да
Асинхронные I/O-операции (await) Нет Да
Комбинация, отмена, цепочки задач Нет Да
Тонкая настройка приоритета и культуры Да (но редко) Нет, только для дефолтных задач
Простое деление работы между ядрами (CPU) Иногда Да

6. Полезные нюансы

Task — это не всегда поток!

Phép màu mạnh nhất: nếu bạn dùng Task cho I/O bất đồng bộ, thường sẽ không tạo thread mới đâu! Mọi thứ "kỳ diệu" chuyển xuống IO Completion Ports hoặc primitive nền tảng khác. Thread được giải phóng khi task chờ cái gì đó bên ngoài: file, mạng, DB. Thực ra, trong thời gian chờ không có thread nào bị block!

Task и асинхронность (I/O-bound) — магия await

using System;
using System.Net.Http;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        // Tải nội dung trang web bất đồng bộ (I/O-bound)
        HttpClient client = new HttpClient();
        string data = await client.GetStringAsync("https://www.dotnetfoundation.org");
        Console.WriteLine($"Số ký tự nhận được: {data.Length}");
    }
}
  • Ở đây task (Task<string>) đóng gói thao tác I/O bất đồng bộ.
  • Thread không bị block — nó tiếp tục làm việc, và khi tải xong thì execution method tiếp tục.
  • Tạo thread thủ công cho việc này là thừa và không hiệu quả.

Task и ThreadPool

Khi bạn gọi Task.Run(...) hoặc dùng async API (await cái gì đó), .NET thường dùng một thread pool — ThreadPool. Đây là tập hợp thread đã được tạo sẵn, "ngồi trên ghế dự bị" và sẵn sàng nhận job. Nếu ít việc — thread rảnh, nếu nhiều việc — pool tăng thread tự động nhưng có điều độ. Nhờ đó app của bạn scale theo số task mà không tạo quá tải cho hệ thống.

Thread tạo bằng new Thread hầu như luôn là một "cư dân" riêng trong hệ — nó không quay trở lại pool sau khi xong mà chết luôn. Đó là lý do Task hiệu quả hơn cho parallelism hàng loạt.

7. Типичные ошибки и подводные камни

Nếu bỗng muốn làm retro-dev và viết mọi thứ bằng threads, bạn sẽ gặp phiêu lưu thú vị: leak memory, đồng bộ hóa phức tạp, không thể cancel, threads "treo" như linh hồn (zombie), bắt và xử lý lỗi cần API đặc biệt.

Điều quan trọng nhớ: "Task" — tiện, an toàn và hiện đại. Trong đa số trường hợp khi phát triển C# hôm nay không có lý do để quay lại quản lý thread thủ công.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION