1. Giới thiệu
Hãy tưởng tượng threads — là những người xếp hàng mua kem. Có người đứng yên, có người ồn ào ("Mình sắp kịp tàu, cho mình qua!"), và người bán có vẻ nghe tất cả nhưng đôi khi phục vụ ai đó ngoài lượt — ví dụ mẹ bế em bé đang khóc. Ưu tiên của thread cũng tương tự như vậy.
Trong C# (chính xác hơn là .NET) mỗi thread có một ưu tiên — một gợi ý cho hệ điều hành biết thread này "quan trọng" đến mức nào so với những thread khác. Đây không phải luật cứng cho OS (không ai đảm bảo thread có ưu tiên cao nhất sẽ luôn nhận toàn bộ sự chú ý), nhưng thường thì thread có ưu tiên cao hơn sẽ được cấp nhiều thời gian CPU hơn.
Ứng dụng thực tế?
- Trong giao diện người dùng: ví dụ, cập nhật màn hình không nên bị chậm bởi các phép tính nặng ưu tiên thấp.
- Trong game: render khung hình quan trọng hơn tạo bản đồ nền.
- Trong các tác vụ cần phản ứng nhanh: điều khiển thiết bị, xử lý tín hiệu, v.v.
2. Ưu tiên của thread: hoạt động như thế nào
Trong C# làm việc với ưu tiên thread khá đơn giản — đối tượng Thread có thuộc tính Priority. Nó nhận các giá trị từ enum ThreadPriority:
| Giá trị | Mô tả |
|---|---|
|
Ưu tiên thấp nhất |
|
Thấp hơn bình thường |
|
Bình thường (mặc định) |
|
Cao hơn bình thường |
|
Ưu tiên cao nhất |
Ví dụ mã:
using System;
using System.Threading;
class Program
{
static void Main()
{
Thread lowPriorityThread = new Thread(PrintLowPriority);
Thread highPriorityThread = new Thread(PrintHighPriority);
lowPriorityThread.Priority = ThreadPriority.Lowest;
highPriorityThread.Priority = ThreadPriority.Highest;
lowPriorityThread.Start();
highPriorityThread.Start();
}
static void PrintLowPriority()
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine("Ưu tiên thấp: " + i);
Thread.Sleep(10); // Thêm tạm dừng để thấy sự khác biệt
}
}
static void PrintHighPriority()
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine("Ưu tiên cao: " + i);
Thread.Sleep(10);
}
}
}
Ghi chú
Trong thực tế, đặc biệt trên hệ đa nhân hiện đại và môi trường quản lý .NET, ưu tiên chỉ là khuyến nghị cho hệ điều hành. OS (và .NET) sẽ cố phân bổ nhiều thời gian hơn cho thread có ưu tiên cao, nhưng không có bảo đảm tuyệt đối. Ví dụ, nếu thread của bạn chiếm toàn bộ CPU với ưu tiên Highest, giao diện có thể bắt đầu lag và các thread khác sẽ chịu ảnh hưởng.
3. Thay đổi ưu tiên của thread
Threads và ưu tiên
graph LR
A[Thread 1 - Lowest] -->|Ít thời gian CPU hơn| OS(Hệ điều hành)
B[Thread 2 - Normal] --> OS
C[Thread 3 - Highest] -->|Nhiều thời gian CPU hơn| OS
OS --> CPU(Bộ xử lý)
Tại sao thay đổi ưu tiên?
Có vẻ hợp lý là luôn đặt Highest, nhưng đó là ý tưởng tệ! Hãy tưởng tượng nếu tất cả khách hàng trong cửa hàng cùng hét "Phục vụ tôi trước!".
Chỉ thay đổi ưu tiên khi có lý do hợp lý:
- Thread ghi log nền — có thể đặt BelowNormal hoặc Lowest.
- Thread xử lý input của người dùng — AboveNormal.
- Các tác vụ tiêu tốn CPU không cần kịp thời — ưu tiên thấp.
Mẹo: Nếu app bắt đầu chậm, kiểm tra xem có thread "thô lỗ" nào đặt ưu tiên cao cản trở các thread khác không!
4. Loại (danh mục) thread trong .NET
Trong C# thường phân biệt hai loại thread: foreground và background. Thú vị là "background" không có nghĩa là nó đang làm việc "ở nền". Bây giờ giải thích rõ.
Foreground threads (chính)
- Mặc định tất cả thread bạn tạo là foreground.
- Khi còn ít nhất một foreground thread sống, ứng dụng sẽ không kết thúc, dù mọi thứ khác đã "chết".
- Ví dụ: thread chính của chương trình (Main), và tất cả thread tạo bằng new Thread() nếu không thay đổi thêm.
Background threads (phụ)
- Nếu tất cả foreground threads kết thúc và chỉ còn background thread — process sẽ kết thúc và các thread đó bị dừng ngay mà không báo trước.
- Dùng cho công việc phụ: logging, gửi metric nền, v.v.
- Để biến thread thành background: gán thuộc tính IsBackground thành true:
Thread t = new Thread(SomeMethod);
t.IsBackground = true;
t.Start();
Minh họa sự khác nhau
using System;
using System.Threading;
class Program
{
static void Main()
{
Thread backgroundThread = new Thread(() =>
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine("Background thread đang chạy... " + i);
Thread.Sleep(500);
}
Console.WriteLine("Background thread ĐÃ KẾT THÚC.");
});
backgroundThread.IsBackground = true; // Biến thread thành background!
backgroundThread.Start();
Console.WriteLine("Main sẽ kết thúc sau 1 giây.");
Thread.Sleep(1000); // Đợi một giây
Console.WriteLine("Main đã kết thúc. Background thread sẽ thế nào?");
}
}
Bạn sẽ thấy gì?
Main có thể kết thúc và background thread sẽ bị "tắt" dở giữa chừng. Điều này tiện cho những tác vụ không cần can thiệp vào việc đóng ứng dụng.
5. Loại thread
ThreadPool (Pool thread)
- ThreadPool — là cơ chế quản lý một tập các thread để không tạo quá nhiều thread mới.
- Dùng khi có nhiều công việc ngắn: xử lý request song song, thao tác async.
- Thread từ pool luôn là background — chúng không ngăn ứng dụng kết thúc.
Ví dụ: chạy code trong thread pool
using System;
using System.Threading;
class Program
{
static void Main()
{
ThreadPool.QueueUserWorkItem(DoWorkInThreadPool, "tác vụ nền");
Console.WriteLine("Main đang kết thúc.");
Thread.Sleep(500);
}
static void DoWorkInThreadPool(object? state)
{
Console.WriteLine("Thread từ pool: " + state);
Thread.Sleep(1000); // Thử ngủ
Console.WriteLine("Thread từ pool đã kết thúc!");
}
}
Lưu ý:
Nếu Main kết thúc sớm hơn, thread từ pool có thể bị dừng. Nếu cần đảm bảo hoàn thành, dùng foreground thread hoặc đợi rõ ràng.
Tiến hóa của đa luồng: Task, async/await
Trong ứng dụng hiện đại hiếm khi tạo thread thủ công (Thread). Thường dùng Task và phương thức bất đồng bộ. Quan trọng cần biết:
- Threads từ pool và Task hoạt động trên background threads.
- Với hầu hết tác vụ không cần thay đổi ưu tiên — tuân theo common sense và best practices.
6. Những chi tiết hữu ích
Thuộc tính và hành vi của thread
| Thuộc tính | Foreground Thread | Background Thread | ThreadPool Thread |
|---|---|---|---|
|
false theo mặc định | true | true |
| Kết thúc ứng dụng | Ứng dụng KHÔNG kết thúc khi còn ít nhất một foreground thread | Ứng dụng kết thúc nếu tất cả foreground threads đã kết thúc | Ứng dụng kết thúc khi Main kết thúc |
| Điều khiển | Kiểm soát đầy đủ | Kiểm soát đầy đủ | Không có kiểm soát trực tiếp |
| Ứng dụng | Các tác vụ dài, quan trọng (ví dụ server DB) | Tác vụ không quan trọng, thao tác nền, logging | Tác vụ ngắn, Task, thao tác async |
| Ưu tiên | Có thể đặt | Có thể đặt | Không khuyến nghị thay đổi |
Mẹo và lưu ý không hiển nhiên
- Chỉ thay đổi ưu tiên cho thread thường (Thread), không cho các task từ pool (Task, ThreadPool).
- Thread từ pool luôn có ưu tiên Normal (không thể thay đổi).
- Task và async/await — cách tiếp cận hiện đại hơn, nơi các vấn đề ưu tiên và thực thi nền được ẩn ở "hậu trường".
7. Lỗi phổ biến với ưu tiên và loại thread
Lỗi #1: lạm dụng ưu tiên.
Gán mọi thread là Highest hoặc mọi thread là Lowest mà không có lý do khách quan thường không ích lợi. Điều này có thể phá vỡ cân bằng thực thi và giảm độ phản hồi của ứng dụng.
Lỗi #2: kết thúc ngầm của background thread.
Nếu công việc quan trọng (ví dụ lưu dữ liệu) chạy trên background thread và bạn không đợi nó hoàn thành, có rủi ro mất dữ liệu. Background thread bị dừng khi process kết thúc.
Lỗi #3: kỳ vọng quá cao vào ưu tiên thread.
Ưu tiên của thread (Priority) chỉ là khuyến nghị cho hệ điều hành, không phải bảo đảm rằng thread sẽ được chạy trước hết.
GO TO FULL VERSION