Gọi (Ủy quyền)


93

Ai có thể vui lòng giải thích tuyên bố này được viết trên liên kết này

Invoke(Delegate):

Thực thi đại biểu được chỉ định trên luồng sở hữu tay cầm cửa sổ cơ bản của điều khiển.

Ai có thể giải thích điều này có nghĩa là gì (đặc biệt là phần in đậm) Tôi không thể hiểu rõ được không


4
Câu trả lời cho câu hỏi này được gắn với thuộc tính Control.InvokeRequired - xem msdn.microsoft.com/en-us/library/…
dash

Câu trả lời:


130

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.


67

Một đối tượng điều khiển hoặc cửa sổ trong Windows Forms chỉ là một trình bao bọc xung quanh cửa sổ Win32 được xác định bởi một tay cầm (đôi khi được gọi là HWND). Hầu hết mọi thứ bạn làm với điều khiển cuối cùng sẽ dẫn đến lệnh gọi Win32 API sử dụng điều khiển này. Xử lý thuộc sở hữu của luồng đã tạo ra nó (thường là luồng chính) và không được thao tác bởi một luồng khác. Nếu vì lý do nào đó bạn cần thực hiện điều gì đó với điều khiển từ một luồng khác, bạn có thể sử dụng Invokeđể yêu cầu luồng chính thực hiện điều đó thay cho bạn.

Ví dụ: nếu bạn muốn thay đổi văn bản của nhãn từ một chuỗi công nhân, bạn có thể làm như sau:

theLabel.Invoke(new Action(() => theLabel.Text = "hello world from worker thread!"));

Bạn có thể giải thích tại sao ai đó lại làm điều gì đó như thế này không? this.Invoke(() => this.Enabled = true);Bất cứ điều gì thisđề cập đến chắc chắn là trong chủ đề hiện tại, phải không?
Kyle Delaney

1
@KyleDelaney, một đối tượng không phải là "trong" một luồng và luồng hiện tại không nhất thiết là luồng đã tạo ra đối tượng.
Thomas Levesque

24

Nếu bạn muốn sửa đổi một điều khiển, nó phải được thực hiện trong luồng mà điều khiển đã được tạo. InvokePhương thức này cho phép bạn thực thi các phương thức trong luồng được liên kết (luồng sở hữu tay cầm cửa sổ bên dưới của điều khiển).

Trong mẫu bên dưới, thread1 ném một ngoại lệ vì SetText1 đang cố gắng sửa đổi textBox1.Text từ một chủ đề khác. Nhưng trong thread2, Action trong SetText2 được thực thi trong thread mà TextBox được tạo

private void btn_Click(object sender, EvenetArgs e)
{
    var thread1 = new Thread(SetText1);
    var thread2 = new Thread(SetText2);
    thread1.Start();
    thread2.Start();
}

private void SetText1() 
{
    textBox1.Text = "Test";
}

private void SetText2() 
{
    textBox1.Invoke(new Action(() => textBox1.Text = "Test"));
}

Tôi thực sự thích cách tiếp cận này, nó ẩn tính chất đại biểu, nhưng dù sao nó cũng là một phím tắt hay.
shytikov

6
Invoke((MethodInvoker)delegate{ textBox1.Text = "Test"; });

Việc sử dụng của người dân System.Action gợi ý về câu trả lời khác chỉ hoạt động trên khuôn khổ 3.5+, cho các phiên bản cũ, điều này hoạt động hoàn hảo
tự sát Platypus

2

Trong điều kiện thực tế, điều đó có nghĩa là đại biểu được đảm bảo sẽ được gọi trên luồng chính. Điều này quan trọng vì trong trường hợp điều khiển cửa sổ nếu bạn không cập nhật thuộc tính của chúng trên luồng chính thì bạn sẽ không thấy thay đổi hoặc điều khiển tạo ra một ngoại lệ.

Mô hình là:

void OnEvent(object sender, EventArgs e)
{
   if (this.InvokeRequired)
   {
       this.Invoke(() => this.OnEvent(sender, e);
       return;
   }

   // do stuff (now you know you are on the main thread)
}

2

this.Invoke(delegate) đảm bảo rằng bạn đang gọi đối số là đối số this.Invoke() trên luồng chính / luồng đã tạo.

Tôi có thể nói rằng quy tắc Ngón tay cái không truy cập các điều khiển biểu mẫu của bạn ngoại trừ từ chuỗi chính.

Có thể những dòng sau hợp lý khi sử dụng Invoke ()

    private void SetText(string text)
    {
        // InvokeRequired required compares the thread ID of the
        // calling thread to the thread ID of the creating thread.
        // If these threads are different, it returns true.
        if (this.textBox1.InvokeRequired)
        {   
            SetTextCallback d = new SetTextCallback(SetText);
            this.Invoke(d, new object[] { text });
        }
        else
        {
            this.textBox1.Text = text;
        }
    }

Có những trường hợp mặc dù bạn tạo một luồng Threadpool (tức là luồng công nhân) nó sẽ chạy trên luồng chính. Nó sẽ không tạo một luồng mới coz luồng chính có sẵn để xử lý các hướng dẫn thêm. Vì vậy, trước tiên hãy điều tra xem luồng đang chạy hiện tại có phải là luồng chính hay không bằng cách sử dụng this.InvokeRequirednếu trả về true, mã hiện tại đang chạy trên luồng worker vì vậy hãy gọi this.Invoke (d, new object [] {text});

khác trực tiếp cập nhật điều khiển giao diện người dùng (Tại đây bạn được đảm bảo rằng bạn đang chạy mã trên chuỗi chính.)


1

Nó có nghĩa là ủy quyền sẽ chạy trên chuỗi giao diện người dùng, ngay cả khi bạn gọi phương thức đó từ một nhân viên nền hoặc chuỗi nhóm luồng. Các phần tử giao diện người dùng có mối quan hệ với luồng - chúng chỉ thích nói chuyện trực tiếp với một luồng: luồng giao diện người dùng. Luồng giao diện người dùng được định nghĩa là luồng đã tạo ra cá thể điều khiển và do đó được liên kết với tay cầm cửa sổ. Nhưng tất cả những điều đó là một chi tiết thực hiện.

Điểm mấu chốt là: bạn sẽ gọi phương thức này từ một chuỗi công nhân để bạn có thể truy cập vào giao diện người dùng (để thay đổi giá trị trong nhãn, v.v.) - vì bạn không được phép làm điều đó từ bất kỳ chuỗi nào khác ngoài chuỗi giao diện người dùng.


0

Đại biểu về cơ bản là nội tuyến Actioncủa hoặc Func<T>. Bạn có thể khai báo một ủy nhiệm bên ngoài phạm vi của một phương thức mà bạn đang chạy hoặc sử dụng lambdabiểu thức ( =>); bởi vì bạn chạy ủy nhiệm trong một phương thức, bạn chạy nó trên luồng đang được chạy cho cửa sổ / ứng dụng hiện tại được in đậm một chút.

Ví dụ Lambda

int AddFiveToNumber(int number)
{
  var d = (int i => i + 5);
  d.Invoke(number);
}

0

Nó có nghĩa là ủy quyền mà bạn vượt qua được thực thi trên luồng đã tạo ra đối tượng Điều khiển (là luồng giao diện người dùng).

Bạn cần gọi phương thức này khi ứng dụng của bạn có nhiều luồng và bạn muốn thực hiện một số hoạt động UI từ một luồng khác với luồng UI, bởi vì nếu bạn chỉ cố gắng gọi một phương thức trên Điều khiển từ một luồng khác, bạn sẽ nhận được System.InvalidOperationException.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.