C# - C Sharp: Đa luồng (MultiThreading)

Các khóa học qua video:
Python SQL Server PHP C# Lập trình C Java HTML5-CSS3-JavaScript
Học trên YouTube <76K/tháng. Đăng ký Hội viên
Viết nhanh hơn - Học tốt hơn
Giải phóng thời gian, khai phóng năng lực

Một thread (luồng) được định nghĩa là đường dẫn thực thi của một chương trình. Mỗi thread định nghĩa một luồng điều khiển duy nhất. Nếu ứng dụng của bạn liên quan đến các thao tác phức tạp và tốn thời gian, thì việc thiết lập các đường dẫn hoặc thread thực thi khác nhau thường hữu ích, với mỗi thread thực hiện một công việc cụ thể.

Thread là tiến trình (process) nhẹ. Một ví dụ phổ biến về việc sử dụng thread là việc thực hiện lập trình đồng thời bởi các hệ điều hành hiện đại. Việc sử dụng thread giúp tiết kiệm lãng phí chu kỳ CPU và tăng hiệu quả của ứng dụng.

Cho đến giờ chúng ta đã viết các chương trình trong đó một tiểu trình chạy như một tiến trình duy nhất là phiên bản đang chạy của ứng dụng. Tuy nhiên, theo cách này, ứng dụng chỉ có thể thực hiện một công việc tại một thời điểm. Để làm cho nó thực thi nhiều tác vụ cùng một lúc, nó có thể được chia thành các thread nhỏ hơn.

Vòng đời của Thread

Vòng đời của một thread bắt đầu khi một đối tượng của lớp System.Threading.Thread được tạo và kết thúc khi thread kết thúc hoặc hoàn thành thực thi.

Sau đây là các trạng thái khác nhau trong vòng đời của một thread:

  • Trạng thái chưa bắt đầu - Đó là tình huống khi thể hiện của thread được tạo nhưng phương thức Start không được gọi.
  • Trạng thái sẵn sàng - Đó là tình huống khi thread sẵn sàng chạy và chờ chu kỳ CPU.
  • Trạng thái không thể chạy được - Một thread không thể thực thi được, khi:
    • Phương thức Sleep đã được gọi
    • Phương thức Wait đã được gọi
    • Bị chặn bởi hoạt động I/O
  • Trạng thái chết - Đó là tình huống khi thread hoàn thành thực thi hoặc bị hủy bỏ.

Thread chính

Trong C#, lớp System.Threading.Thread được sử dụng để làm việc với các thread. Nó cho phép tạo và truy cập các thread riêng lẻ trong một ứng dụng multithreading (đa luồng). Thread đầu tiên được thực thi trong một tiến trình được gọi là thread chính.

Khi chương trình C# bắt đầu thực thi, thread chính được tạo tự động. Các thread được tạo bằng lớp Thread được gọi là các thread con của thread chính. Bạn có thể truy cập một thread bằng thuộc tính CurrentThread của lớp Thread.

Chương trình sau minh họa thực thi thread chính:

using System;
using System.Threading;

namespace MultithreadingApplication {
   class MainThreadProgram {
      static void Main(string[] args) {
         Thread th = Thread.CurrentThread;
         th.Name = "MainThread";
         
         Console.WriteLine($"This is {th.Name}");
         Console.ReadKey();
      }
   }
}

Kết quả:

This is MainThread

Các thuộc tính và phương thức của lớp Thread

Bảng sau đây cho thấy một số thuộc tính được sử dụng phổ biến nhất của lớp Thread:

STT Thuộc tính & mô tả
1

CurrentContext

Lấy ngữ cảnh hiện tại mà thread đang thực thi.

2

CurrentCulture

Nhận hoặc đặt culture cho thread hiện tại.

3

CurrentPrinciple

Nhận hoặc đặt quy tắc hiện tại của chuỗi (để bảo mật dựa trên vai trò).

4

CurrentThread

Nhận thread hiện đang chạy.

5

CurrentUICulture

Nhận hoặc đặt culture hiện tại được Trình quản lý tài nguyên sử dụng để tra cứu các tài nguyên dành riêng cho culture trong thời gian chạy.

6

ExecutionContext

Nhận một đối tượng ExecutionContext chứa thông tin về các ngữ cảnh khác nhau của thread hiện tại.

7

IsAlive

Nhận một giá trị cho biết trạng thái thực thi của thread hiện tại.

8

IsBackground

Nhận hoặc đặt giá trị cho biết thread có phải là thread nền hay không.

9

IsThreadPoolThread

Nhận một giá trị cho biết một thread có thuộc nhóm thread được quản lý hay không.

10

ManagedThreadId

Nhận một mã định danh duy nhất cho thread được quản lý hiện tại.

11

Name

Nhận hoặc đặt tên của thread.

12

Priority

Nhận hoặc đặt một giá trị cho biết mức độ ưu tiên lập lịch trình của một thread.

13

ThreadState

Nhận một giá trị chứa các trạng thái của thread hiện tại.

Bảng sau đây cho thấy một số phương thức được sử dụng phổ biến nhất của lớp Thread

STT Phương thức & mô tả
1

Abort()

Tăng một ThreadAbortException trong thread mà nó được gọi để bắt đầu quá trình kết thúc thread. Gọi phương thức này thường chấm dứt thread.

2

LocalDataStoreSlot AllocateDataSlot()

Phân bổ một slot dữ liệu chưa được đặt tên trên tất cả các thread. Để có hiệu suất tốt hơn, thay vào đó hãy sử dụng các trường được đánh dấu bằng thuộc tính ThreadStaticAttribute.

3

LocalDataStoreSlot AllocateNamedDataSlot(string name)

Phân bổ một slot dữ liệu được đặt tên trên tất cả các thread. Để có hiệu suất tốt hơn, thay vào đó hãy sử dụng các trường được đánh dấu bằng thuộc tính ThreadStaticAttribute.

4

BeginCriticalRegion()

Thông báo cho máy chủ lưu trữ rằng quá trình thực thi sắp đi vào một vùng mã trong đó tác động của việc hủy bỏ thread hoặc ngoại lệ chưa được xử lý có thể gây nguy hiểm cho các tác vụ khác trong miền ứng dụng.

5

BeginThreadAffinity()

Thông báo cho máy chủ biết rằng mã được quản lý sắp thực thi các lệnh phụ thuộc vào danh tính của thread hệ điều hành vật lý hiện tại.

6

EndCriticalRegion()

Thông báo cho máy chủ lưu trữ rằng quá trình thực thi sắp đi vào một vùng mã trong đó tác động của việc hủy bỏ thread hoặc ngoại lệ chưa được xử lý bị giới hạn đối với tác vụ hiện tại.

7

EndThreadAffinity()

Thông báo cho máy chủ lưu trữ rằng mã được quản lý đã thực hiện xong các hướng dẫn phụ thuộc vào danh tính của thread hệ điều hành vật lý hiện tại.

8

FreeNamedDataSlot(tên chuỗi)

Loại bỏ sự liên kết giữa tên và vị trí, cho tất cả các thread trong quy trình. Để có hiệu suất tốt hơn, thay vào đó hãy sử dụng các trường được đánh dấu bằng thuộc tính ThreadStaticAttribute.

9

GetData (LocalDataStoreSlot slot)

Truy xuất giá trị từ vị trí đã chỉ định trên thread hiện tại, trong miền hiện tại của thread hiện tại. Để có hiệu suất tốt hơn, thay vào đó hãy sử dụng các trường được đánh dấu bằng thuộc tính ThreadStaticAttribute.

10

GetDomain()

Trả về miền hiện tại mà thread hiện tại đang chạy.

11

GetDomainID()

Trả về một mã định danh miền ứng dụng duy nhất

12

GetNamedDataSlot(string name)

Tra cứu một slot dữ liệu được đặt tên. Để có hiệu suất tốt hơn, thay vào đó hãy sử dụng các trường được đánh dấu bằng thuộc tính ThreadStaticAttribute.

13

Interrupt()

Ngắt thread đang ở trạng thái thread WaitSleepJoin.

14

Join()

Chặn thread đang gọi cho đến khi một thread kết thúc, trong khi tiếp tục thực hiện bơm (pumping) COM và SendMessage chuẩn. Phương thức này có các dạng nạp chồng khác nhau.

15

MemoryBarrier()

Đồng bộ hóa truy cập bộ nhớ như sau: Bộ xử lý thực thi thread hiện tại không thể sắp xếp lại các hướng dẫn theo cách mà bộ nhớ truy cập trước lệnh gọi MemoryBarrier thực thi sau khi truy cập bộ nhớ theo lệnh gọi MemoryBarrier.

16

ResetAbort()

Hủy bỏ yêu cầu Abort cho thread hiện tại.

17

SetData(LocalDataStoreSlot slot, Object data)

Đặt dữ liệu trong vị trí đã chỉ định trên thread hiện đang chạy, cho miền hiện tại của thread đó. Để có hiệu suất tốt hơn, hãy sử dụng các trường được đánh dấu bằng thuộc tính ThreadStaticAttribute để thay thế.

18

Start()

Bắt đầu một thread.

19

Sleep(int milisecondsTimeout)

Tạm dừng thread trong một khoảng thời gian.

20

SpinWait (int interation)

Làm cho một thread đợi số lần được xác định bởi tham số interation

21

VolatileRead(ref byte address)

VolatileRead (ref double address)

VolatileRead(ref int address)

VolatileRead(ref Object address)

Đọc giá trị của một trường. Giá trị là giá trị mới nhất được ghi bởi bất kỳ bộ xử lý nào trong máy tính, bất kể số lượng bộ xử lý hoặc trạng thái bộ đệm của bộ xử lý. Phương thức này có các dạng nạp chồng khác nhau. Ở trên chỉ đưa ra bốn trong số đó.

22

VolatileWrite(ref by address, byte value)

VolatileWrite(ref double address, double value)

VolatileWrite(ref int address, int value)

VolatileWrite(ref Object address, Object value)

Viết một giá trị vào một trường ngay lập tức để tất cả các bộ xử lý trong máy tính có thể nhìn thấy giá trị đó. Phương thức này có các dạng nạp chồng khác nhau. Ở trên chỉ đưa ra bốn trong số đó.

23

Yield()

Làm cho thread đang gọi nhường quyền thực thi cho một thread khác sẵn sàng chạy trên bộ xử lý hiện tại. Hệ điều hành chọn thread để nhường chỗ.

Tạo thread

Thread được tạo bằng cách thừa kế lớp Thread. Lớp thừa kế từ Thread sau đó sẽ gọi phương thức Start() để bắt đầu thực thi thread con.

Sau đây là ví dụ minh họa:

using System;
using System.Threading;

namespace MultithreadingApplication {
   class ThreadCreationProgram {
      public static void CallToChildThread() {
         Console.WriteLine("Child thread starts");
      }

      static void Main(string[] args) {
         ThreadStart childref = new ThreadStart(CallToChildThread);
         Console.WriteLine("In Main: Creating the Child thread");
         Thread childThread = new Thread(childref);
         childThread.Start();
      }
   }
}

Kết quả:

In Main: Creating the Child thread
Child thread starts

Quản lý thread

Lớp Thread cung cấp các phương thức khác nhau để quản lý các thread.

Ví dụ sau minh họa việc sử dụng phương thức Sleep() để tạm dừng một thread trong một khoảng thời gian cụ thể.

using System;
using System.Threading;

namespace ConsoleApp1
{
 internal class Program
 {
  public static void CallToChildThread()
  {
   Console.WriteLine("Child thread starts");

   // dừng thread trong 5000 milli giây
   int sleepfor = 5000;

   Console.WriteLine($"Child Thread Paused for {sleepfor / 1000} seconds");
   Thread.Sleep(sleepfor);
   Console.WriteLine("Child thread resumes");
  }

  static void Main(string[] args)
  {
   ThreadStart childref = new ThreadStart(CallToChildThread);
   Console.WriteLine("In Main: Creating the Child thread");

   Thread childThread = new Thread(childref);
   childThread.Start();
  }
 }
}

Kết quả:

In Main: Creating the Child thread
Child thread starts
Child Thread Paused for 5 seconds
Child thread resumes

Hủy thread

Phương thức Abort() được sử dụng để hủy các thread.

Bộ thực thi hủy bỏ thread bằng cách đưa ra một ThreadAbortException. Ngoại lệ này không thể bị bắt, điều khiển được gửi đến khối finally nếu có.

Chương trình sau đây minh họa điều này:

using System;
using System.Threading;

namespace ConsoleApp1
{
 internal class Program
 {
  public static void CallToChildThread()
  {
   try
   {
    Console.WriteLine("Child thread starts");

    // thực hiện việc hiển thị các số từ 0 đến 10
    for (int counter = 0; counter <= 10; counter++)
    {
     Thread.Sleep(500);
     Console.WriteLine(counter);
    }

    Console.WriteLine("Child Thread Completed");
   }
   catch (ThreadAbortException e)
   {
    Console.WriteLine("Thread Abort Exception");
   }
   finally
   {
    Console.WriteLine("Couldn't catch the Thread Exception");
   }
  }

  static void Main(string[] args)
  {
   ThreadStart childref = new ThreadStart(CallToChildThread);
   Console.WriteLine("In Main: Creating the Child thread");

   Thread childThread = new Thread(childref);
   childThread.Start();

   //dừng thread chính trong 2 giây
   Thread.Sleep(2000);

   //tiến hành hủy thread con
   Console.WriteLine("In Main: Aborting the Child thread");
   childThread.Abort();
  }
 }
}

Kết quả:

In Main: Creating the Child thread
Child thread starts
0
1
2
In Main: Aborting the Child thread
Thread Abort Exception
Couldn't catch the Thread Exception 
» Tiếp: Lập trình song song (Concurrency programming)
« Trước: Các mức khả năng truy cập
Các khóa học qua video:
Python SQL Server PHP C# Lập trình C Java HTML5-CSS3-JavaScript
Học trên YouTube <76K/tháng. Đăng ký Hội viên
Viết nhanh hơn - Học tốt hơn
Giải phóng thời gian, khai phóng năng lực
Copied !!!