Câu trả lời cho câu hỏi này nằm ở cách C # Controls hoạt động
Các điều khiển trong Windows Forms được liên kết với một chuỗi cụ thể và không an toàn cho chuỗi. Do đó, nếu bạn đang gọi phương thức của điều khiển từ một luồng khác, bạn phải sử dụng một trong các phương thức gọi của điều khiển để điều khiển cuộc gọi đến luồng thích hợp. Thuộc tính này có thể được sử dụng để xác định xem bạn có phải gọi một phương thức gọi hay không, điều này có thể hữu ích nếu bạn không biết luồng sở hữu điều khiển nào.
Từ Control.InvokeRequired
Về mặt hiệu quả, những gì Invoke làm là đảm bảo rằng mã bạn đang gọi xảy ra trên luồng mà điều khiển "sống trên" ngăn chặn hiệu quả các ngoại lệ theo luồng chéo.
Từ góc độ lịch sử, trong .Net 1.1, điều này đã thực sự được phép. Điều đó có nghĩa là bạn có thể thử và thực thi mã trên luồng "GUI" từ bất kỳ luồng nền nào và điều này hầu hết sẽ hoạt động. Đôi khi nó chỉ khiến ứng dụng của bạn thoát ra vì bạn đang làm gián đoạn chuỗi GUI trong khi nó đang làm việc khác. Đây là ngoại lệ Cross Threaded - hãy tưởng tượng đang cố gắng cập nhật TextBox trong khi GUI đang vẽ thứ gì đó khác.
- Hành động nào được ưu tiên?
- Liệu cả hai có thể xảy ra cùng một lúc?
- Điều gì xảy ra với tất cả các lệnh khác mà GUI cần chạy?
Thực tế, bạn đang làm gián đoạn hàng đợi, điều này có thể gây ra nhiều hậu quả không lường trước được. Invoke thực sự là một cách "lịch sự" để đưa những gì bạn muốn làm vào hàng đợi đó và quy tắc này đã được thực thi từ .Net 2.0 trở đi thông qua một InvalidOperationException được ném ra .
Để hiểu những gì đang thực sự diễn ra đằng sau hậu trường và ý nghĩa của "GUI Thread", sẽ rất hữu ích khi hiểu Message Pump hoặc Message Loop là gì.
Điều này thực sự đã được trả lời trong câu hỏi " Máy bơm thông báo là gì " và bạn nên đọc để hiểu cơ chế thực tế mà bạn đang buộc vào khi tương tác với các điều khiển.
Các bài đọc khác mà bạn có thể thấy hữu ích bao gồm:
Có chuyện gì với Begin Invoke
Một trong những quy tắc cơ bản của lập trình Windows GUI là chỉ luồng đã tạo điều khiển mới có thể truy cập và / hoặc sửa đổi nội dung của nó (ngoại trừ một vài trường hợp ngoại lệ được ghi lại). Hãy thử làm điều đó từ bất kỳ chuỗi nào khác và bạn sẽ nhận được các hành vi không thể đoán trước, từ bế tắc, ngoại lệ đến giao diện người dùng được cập nhật một nửa. Sau đó, cách phù hợp để cập nhật một điều khiển từ một chuỗi khác là đăng một thông báo thích hợp vào hàng đợi thông báo ứng dụng. Khi máy bơm thông báo bắt đầu thực hiện thông báo đó, điều khiển sẽ được cập nhật, trên cùng một luồng đã tạo ra nó (hãy nhớ rằng máy bơm thông báo chạy trên luồng chính).
và để có cái nhìn tổng quan hơn về mã với mẫu đại diện:
Hoạt động xuyên chuỗi không hợp lệ
// the canonical form (C# consumer)
public delegate void ControlStringConsumer(Control control, string text); // defines a delegate type
public void SetText(Control control, string text) {
if (control.InvokeRequired) {
control.Invoke(new ControlStringConsumer(SetText), new object[]{control, text}); // invoking itself
} else {
control.Text=text; // the "functional part", executing only on the main thread
}
}
Khi bạn đánh giá cao InvokeRequired, bạn có thể muốn xem xét sử dụng một phương thức mở rộng để kết thúc các cuộc gọi này. Điều này hoàn toàn được đề cập trong câu hỏi Tràn Ngăn xếp Làm sạch Mã Rải rác với Yêu cầu Gọi .
Ngoài ra còn có một bản viết thêm về những gì đã xảy ra trong lịch sử mà có thể được quan tâm.