Khám niệm Multitasking
Trước khi tìm hiểu về Multithreading trong C#, chúng ta sẽ tìm hiểu về Multitasking (đa tác vụ), khái niệm đa tác vụ có nghĩa là thực hiện nhiều tác vụ đồng thời. Hệ điều hành như Windows cũng là một hệ điều hành đa tác vụ, chúng ta có thể thấy các ứng dụng trên Windows có thể chạy cùng lúc với nhau và thực hiện các chức năng của riêng chúng. Lấy một ví dụ, trên máy tính có thể cùng lúc mở ứng dụng Chrome, NotePad, Microsoft Word,… một cách đồng thời.
Như vậy Multitasking đề cập đến việc thực hiện nhiều tác vụ hoặc tiến trình đồng thời trên một hệ thống máy tính. Nó cho phép máy tính thực hiện nhiều tác vụ được kiểm soát bởi một CPU duy nhất. Multitasking là một tính năng cơ bản của các hệ hệ hành hiện đại ngày nay, đóng vai trò quan trọng trong việc cải thiện hiệu suất, tăng cường sự đáp ứng và khả năng sử dụng của hệ thống máy tính.
Process là gì ?
Process (hay còn gọi là tiến trình) là một phần của hệ điều hành, nó chịu trách nhiệm cho việc thực thi các chương trình hoặc ứng dụng. Vì vậy, ta nói việc thực khi một chương trình hay một ứng dụng cũng có nghĩa là đang thực thi tiến trình.
Trong một hệ điều hành (OS), tiến trình là một khái niệm cơ bản nó biểu thị quá trình thực thi của một chương trình cụ thể. Tiến trình là đơn vị nhỏ nhất trong việc thực thi của hệ thống máy tính. Mỗi tiến trình đều có một không gian bộ nhớ riêng để chứa code thực thi, dữ liệu hoặc một ngăn xếp để quản lý các hàm và biến. Hệ điều hành có trách nhiệm quản lý các tiến trình và có những resource riêng như CPU Time, thông tin trạng thái của hệ thống.
Trên hệ điều hành Windows bạn sẽ thấy được các thông tin về tiến trình trong Task Manager

Chúng ta có thể thấy mỗi ứng dụng là một process, ngoài ra còn nhiều các process nền (background process) của hệ điều hành đang chạy ngầm, đó là những Service quan trọng của hệ điều hành.
Như vậy, một hệ điều hành sẽ có các tiến trình hoạt động bên dưới để khởi động một chương trình hoặc một ứng dụng. Còn trong lập trình để khởi chạy đoạn code của một chương trình hoặc ứng dụng thì chúng ta sẽ cần một tiến trình nội bộ được gọi là Thread. Và từ đây chúng ta lại có thể tạo nhiều Thread để chạy cho nhiều đoạn code trong một chương trình thì gọi là Multi-Thread, và đó là Multithreading.
Thread là gì ?
Thread là một tiến trình nhẹ, hay có thể nói Thread là một đơn vị của tiến trình chịu trách nhiệm thực thi code của ứng dụng, nó đảm nhiệm việc xử lý các logic trong đoạn code của ứng dụng. Trong một hệ điều hành Thread là một đơn vị nhỏ hơn tiến trình và được gọi là “lightweight processes”, Thread được chia sẻ cùng không gian bộ nhớ với một tiến trình cha các thành phần như code, data và resource. Tuy nhiên Thread vấn có một bối cảnh thực thi riêng của nó như bộ đếm (counter), thanh ghi (register) và ngăn xếp (stack). Các Thread nằm trong cùng một tiến trình có thể thực thi đồng thời, cho phép thực thi song song các tác vụ.
Mặc định, mỗi tiến trình đều có ít nhất một Thread đảm nhiệm việc thực thi code, và Thread đó được gọi là Main Thread. Vì thế mỗi một ứng dụng mặc định đều có một Thread đơn (single-thread).

Ở hình trên, là một đoạn chương trình mặc định được gọi là chương Main, nó được thực thi bởi Main Thread vì khi chạy ứng dụng lên hàm Main sẽ được gọi.
Bây giờ chúng ta hãy đặt một break point và chạy chương trình Debug. Sau đó vào Debug > Windows > Thread (hoặc dùng Ctrl + Alt + H) để xem thông tin Thread

Khi chương trình đang thực thi Thread nào thì sẽ có dấu mũi tên trỏ đến Thread đó, như hình bên dưới chúng ta có thể thấy chương trình đang chạy đến Thread có tên là Main Thread. Điều này có được là do chúng ta đặt break point tại phần code của Main Thread

Bây giờ chúng ta sẽ tìm hiểu sâu hơn về Thread trong C#
Vì sao chúng ta cần đến Multithread
Mọi chương trình ban đầu đều có một Thread chính và thực hiện từ trên xuống dưới. Hãy xem ví dụ bên dưới, trong phương thức Main có gọi lần lượt 3 phương thức PrintNumberMethod1, PrintNumberMethod2, PrintNumberMethod3 mỗi phương thức có trách nhiệm in các số (1-3) (4-7) và (7-10) và chương trình này hoàn toàn chỉ dùng 1 Thread chính duy nhất. Và do đó các phương thức được thực hiện một cách tuần tự và kết quả thu được sẽ là từ 1 đến 10 theo thứ tự tăng dần.
internal class Program
{
static void Main(string[] args)
{
PrintNumberMethod1();
PrintNumberMethod2();
PrintNumberMethod3();
}
private static void PrintNumberMethod1()
{
for (int i = 0; i < 3; i++)
{
Console.WriteLine(i+1);
}
}
private static void PrintNumberMethod2()
{
for (int i = 3; i < 7; i++)
{
Console.WriteLine(i + 1);
}
}
private static void PrintNumberMethod3()
{
for (int i = 7; i < 10; i++)
{
Console.WriteLine(i + 1);
}
}
}
Kết quả thu được

Vậy câu hỏi đặt ra nếu chúng ta muốn cả 3 phương thức đều chạy một cách đồng thời hay nói chính xác hơn là không tuần tự để in kết quả riêng biệt của mỗi hàm và không quan tâm đến thứ tự thì làm thế nào ? Và câu trả lời là sử dụng kỹ thuật MultiThreading.
Tìm hiểu Thread Class
Trước khi băt đầu tìm hiểu cách dùng của MultiThreading, chúng ta sẽ nhìn một số Constructor của lớp: System.Threading.Thread. Các constructor này sẽ phục vụ tạo một Thread mới theo nhu cầu riêng

Ở bài viết này, mình chỉ giới thiệu hai Constructor thường dùng đó là (1) và (3) thứ tự trên hình từ trên xuống
Sử dụng ThreadStart
ThreadStart là một delegate nằm trong lớp Threading

Hãy xem ví dụ cách tạo một ThreadStart bên dưới, chúng ta tạo một ThreadStart có một phương thức đại diện là ThreadAction
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
Thread t = new(new ThreadStart(ThreadAction));
t.Start(); // Bắt đầu chạy Thread
}
private static void ThreadAction()
{
Console.WriteLine("Hello from new Thread !");
}
}
Kết quả thu được

Tiếp theo khi chạy chương trình và đặt Break Point tại hai đoạn code thì chúng ta thấy chương trình sẽ chạy từ Main Thread sang Thread mới (có ID là 3) và mặc định Thread chưa được đặt tên nên nên cột Name sẽ là <No Name>

Sử dụng ParameterizedThreadStart
Đây là một hàm khởi tạo Thread dạng delegate có truyền tham số. Ví dụ bên dưới là code tạo ra một Thread mới có truyền tham số làm một chuỗi có nội dung “laptrinhdotnet.com” và sau đó method đại diện sẽ in dòng chữ bao gồm tham số chuỗi truyền qua.
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
Thread t = new(new ParameterizedThreadStart(ThreadActionWithParams));
t.Start("laptrinhdotnet.com"); // Bắt đầu chạy Thread và truyền tham số
}
private static void ThreadActionWithParams(object? obj)
{
Console.WriteLine($"Hello {obj} from new Thread !");
}
}
Kết quả thu được:

Vậy khi áp dụng vào ví dụ ban đầu ta sẽ tạo 3 Thread riêng để khởi chạy 3 phương thức
internal class Program
{
static void Main(string[] args)
{
// Khởi tạo 3 thread riêng
Thread thread1 = new(PrintNumberMethod1)
{
Name = "Thread1" // đặt tên cho thread thứ nhất
};
Thread thread2 = new(PrintNumberMethod2)
{
Name = "Thread2" // đặt tên cho thread thứ hai
};
Thread thread3 = new(PrintNumberMethod3)
{
Name = "Thread3" // đặt tên cho thread thứ nhất
};
// Khởi chạy 3 thread vừa tạo
thread1.Start();
thread2.Start();
thread3.Start();
}
private static void PrintNumberMethod1()
{
for (int i = 0; i < 3; i++)
{
Console.WriteLine(i+1);
}
}
private static void PrintNumberMethod2()
{
for (int i = 3; i < 7; i++)
{
Console.WriteLine(i + 1);
}
}
private static void PrintNumberMethod3()
{
for (int i = 7; i < 10; i++)
{
Console.WriteLine(i + 1);
}
}
}
Kết quả thu được

Khi chạy chương trình trên chúng ta sẽ thấy mỗi lần chạy sẽ cho ra một kết quả khác nhau, điều này là do các Thread được khởi chạy riêng lẻ và chúng không phụ thuộc vào vì vậy kết quả thu được sẽ không bắt buộc phải tuân theo thứ tự.
Bài viết này chỉ giới thiệu một cách khái quát và cơ bản về Multithreading trong C#, nên sẽ không đi sâu vào chi tiết và các ví dụ cụ thể ở nhiều bối cảnh. Mình sẽ update cho bài viết sau. Xin cảm ơn