Đa luồng có thể cải thiện hiệu suất của các ứng dụng Windows Forms, nhưng việc truy cập vào các điều khiển của Windows Forms không phải là chủ đề an toàn. Đa luồng có thể làm lộ mã của bạn thành các lỗi rất nghiêm trọng và phức tạp. Hai hoặc nhiều luồng điều khiển một điều khiển có thể buộc điều khiển vào trạng thái không nhất quán và dẫn đến tình trạng race, bế tắc và đóng băng hoặc treo. Nếu bạn triển khai đa luồng trong ứng dụng của mình, hãy nhớ gọi điều khiển đa luồng theo cách an toàn cho luồng. Để biết thêm thông tin, hãy xem Thực hành tốt nhất về quản lý luồng.
Có hai cách để gọi điều khiển Windows Forms một cách an toàn từ một luồng không tạo điều khiển đó. Bạn có thể sử dụng phương thức System.Windows.Forms.Control.Invoke
để gọi một đại biểu được tạo trong luồng chính, lần lượt gọi điều khiển. Hoặc, bạn có thể triển khai System.ComponentModel.BackgroundWorker
, sử dụng mô hình hướng sự kiện để tách công việc được thực hiện trong luồng chạy nền khỏi báo cáo kết quả.
1. Unsafe cross-thread calls - Cuộc gọi giữa các luồng không an toàn
Sẽ không an toàn khi gọi điều khiển trực tiếp từ một luồng không tạo ra nó. Đoạn mã sau minh họa một cuộc gọi không an toàn đến điều khiển System.Windows.Forms.TextBox
. Trình xử lý sự kiện Button1_Click
tạo ra một luồng WriteTextUnsafe
mới, trực tiếp đặt thuộc tính TextBox.Text
của luồng chính.
Trình gỡ lỗi Visual Studio phát hiện các cuộc gọi luồng không an toàn này bằng cách đưa ra một InvalidOperationException
với thông báo, Cross-thread operation not valid. Control “” accessed from a thread other than the thread it was created on. InvalidOperationException luôn xảy ra đối với các cuộc gọi đa luồng không an toàn trong quá trình gỡ lỗi Visual Studio và có thể xảy ra khi chạy ứng dụng. Bạn nên khắc phục sự cố, nhưng bạn có thể vô hiệu hóa ngoại lệ bằng cách đặt thuộc tính Control.CheckForIllegalCrossThreadCalls
thành false.
2. Safe cross-thread calls - Cuộc gọi giữa các luồng an toàn
Các ví dụ mã sau đây trình bày hai cách để gọi điều khiển Windows Forms một cách an toàn từ một luồng không tạo ra nó:
- Phương thức System.Windows.Forms.Control.Invoke, gọi một ủy nhiệm từ luồng chính để gọi điều khiển.
- Một thành phần System.ComponentModel.BackgroundWorker, cung cấp một mô hình hướng sự kiện.
Trong cả hai ví dụ, luồng background sleeps trong một giây để mô phỏng công việc đang được thực hiện trong luồng đó.
Bạn có thể xây dựng và chạy các ví dụ này dưới dạng các ứng dụng .NET Framework từ dòng lệnh C# hoặc Visual Basic. Để biết thêm thông tin, hãy xem Xây dựng dòng lệnh với csc.exe hoặc Build từ dòng lệnh (Visual Basic).
Bắt đầu với .NET Core 3.0, bạn cũng có thể xây dựng và chạy các ví dụ dưới dạng ứng dụng Windows .NET Core từ một thư mục có tệp .NET Core Windows Forms
3. Sử dụng phương thức Invoke với một delegate
Ví dụ sau đây minh họa một mẫu để đảm bảo các cuộc gọi an toàn luồng cho điều khiển Windows Forms. Nó truy vấn thuộc tính System.Windows.Forms.Control.InvokeRequired
, so sánh ID luồng tạo của điều khiển với ID luồng gọi. Nếu ID luồng giống nhau, nó gọi điều khiển trực tiếp. Nếu ID luồng khác nhau, nó gọi phương thức Control.Invoke
với một ủy nhiệm từ luồng chính, thực hiện cuộc gọi thực tế đến điều khiển.
SafeCallDelegate
cho phép thiết lập thuộc tính Text của điều khiển TextBox. Phương thức WriteTextSafe
truy vấn InvokeRequired. Nếu InvokeRequired
trả về true
, WriteTextSafe
sẽ chuyển SafeCallDelegate
sang phương thức Invoke
để thực hiện cuộc gọi thực tế đến điều khiển. Nếu InvokeRequired
trả về false
, WriteTextSafe
sẽ đặt TextBox.Text trực tiếp. Trình xử lý sự kiện Button1_Click
tạo luồng mới và chạy phương thức WriteTextSafe
.
https://github.com/search?q=%22void+SetPropertyThreadSafe%22&type=Code
4. Sử dụng trình xử lý sự kiện BackgroundWorker
Một cách dễ dàng để thực hiện đa luồng là với thành phần System.ComponentModel.BackgroundWorker
, sử dụng mô hình hướng sự kiện. Chủ đề Background chạy sự kiện BackgroundWorker.DoWork, không tương tác với chủ đề chính. Chuỗi chính chạy các trình xử lý sự kiện BackgroundWorker.ProgressChanged và BackgroundWorker.RunWorkerCompleted, có thể gọi các điều khiển của luồng chính.
Để thực hiện cuộc gọi an toàn luồng bằng cách sử dụng BackgroundWorker, hãy tạo một phương thức trong luồng background để thực hiện công việc và liên kết nó với sự kiện DoWork. Tạo một phương thức khác trong luồng chính để báo cáo kết quả của công việc background và liên kết nó với sự kiện ProgressChanged hoặc RunWorkerCompleted. Để bắt đầu chuỗi nền, hãy gọi BackgroundWorker.RunWorkerAsync.
Ví dụ sử dụng trình xử lý sự kiện RunWorkerCompleted để đặt thuộc tính Text của điều khiển TextBox. Để biết ví dụ sử dụng sự kiện ProgressChanged
, hãy xem BackgroundWorker
.
Tham khảo:
- How to: Make thread-safe calls to Windows Forms controls
- Multithreading – Lập trình đa luồng, đa tiến trình
- Giới thiệu và hướng dẫn sử dụng Thread và Multi Thread, Process trong visual dot net
- Threading - Nền tảng lập trình C#