Виклик (делегат)


93

Хтось може пояснити це твердження, написане за цим посиланням

Invoke(Delegate):

Виконує вказаний делегат для потоку, який є власником основного дескриптора вікна елемента керування.

Хтось може пояснити, що це означає (особливо сміливий), я не можу зрозуміти це чітко


4
Відповідь на це запитання пов’язана з властивістю Control.InvokeRequired - див. Msdn.microsoft.com/en-us/library/…
тире

Відповіді:


131

Відповідь на це запитання полягає у тому, як працюють елементи керування C #

Елементи керування у Windows Forms прив'язані до певного потоку та не є безпечними для потоків. Отже, якщо ви викликаєте метод управління з іншого потоку, ви повинні використовувати один із методів виклику елемента керування для маршалювання виклику відповідного потоку. За допомогою цієї властивості можна визначити, чи потрібно викликати метод виклику, що може бути корисним, якщо ви не знаєте, якому потоку належить елемент керування.

З Control.InvokeRequired

Фактично те, що Invoke робить, - це те, що код, який ви викликаєте, з’являється у потоці, на якому керування «живе», ефективно запобігаючи виняткам між потоками.

З історичної точки зору в .Net 1.1 це було насправді дозволено. Це означало, що ви можете спробувати виконати код у потоці "GUI" з будь-якого фонового потоку, і це в основному буде працювати. Іноді це може просто спричинити вихід вашої програми, оскільки ви фактично переривали графічний інтерфейс, коли він робив щось інше. Це виняток з перехресними потоками - уявіть, спробуйте оновити TextBox, поки графічний інтерфейс малює щось інше.

  • Які дії мають пріоритет?
  • Чи можливо взагалі обидва трапитися?
  • Що відбувається з усіма іншими командами, необхідними для запуску графічного інтерфейсу?

Фактично ви перериваєте чергу, що може мати багато непередбачених наслідків. Invoke - це фактично "ввічливий" спосіб отримати те, що ви хочете зробити, до цієї черги, і це правило було застосовано з .Net 2.0 далі через викинутий InvalidOperationException .

Щоб зрозуміти, що насправді відбувається за лаштунками, і що мається на увазі під "GUI Thread", корисно зрозуміти, що таке Message Pump або Message Loop.

На це насправді вже дано відповідь у питанні " Що таке насос для повідомлень ", і рекомендується прочитати його, щоб зрозуміти фактичний механізм, з яким ви пов'язані при взаємодії з елементами управління.

Інше читання, яке може вам виявитися корисним, включає:

Що з Begin Invoke

Одним з основних правил програмування графічного інтерфейсу Windows є те, що лише потік, який створив елемент керування, може отримати доступ та / або змінити його вміст (за винятком деяких задокументованих винятків). Спробуйте зробити це з будь-якого іншого потоку, і ви отримаєте непередбачувану поведінку, починаючи від глухого кута і закінчуючи винятками та наполовину оновленим інтерфейсом. Правильним способом оновлення елемента керування з іншого потоку є розміщення відповідного повідомлення в черзі повідомлень програми. Коли насос повідомлення переходить до виконання цього повідомлення, елемент керування оновлюється в тому самому потоці, який його створив (пам’ятайте, насос повідомлення працює в основному потоці).

і для більш детального огляду коду з репрезентативним зразком:

Недійсні крос-потокові операції

// 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
    }
}

Отримавши вдячність за InvokeRequired, ви можете розглянути можливість використання методу розширення для завершення цих викликів. Це детально висвітлено у питанні щодо переповнення стека. Очищення коду, заповненого Invoke .

Існує також подальший запис того, що історично сталося, що може представляти інтерес.


68

Об'єкт керування або вікна у Windows Forms - це просто обгортка навколо вікна Win32, ідентифікованого дескриптором (іноді його називають HWND). Більшість речей, які ви робите з елементом управління, врешті-решт призведе до виклику Win32 API, який використовує цей дескриптор. Дескриптор належить потоку, який його створив (як правило, основний потік), і не повинен ним маніпулювати інший потік. Якщо з якихось причин вам потрібно щось зробити з елементом управління з іншого потоку, ви можете використати, Invokeщоб попросити основний потік зробити це від вашого імені.

Наприклад, якщо ви хочете змінити текст мітки з робочого потоку, ви можете зробити щось подібне:

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

Чи можете ви пояснити, чому хтось може робити щось подібне? this.Invoke(() => this.Enabled = true);Все, thisдо чого йдеться, безумовно, знаходиться в поточній темі, чи не так?
Kyle Delaney

1
@KyleDelaney, об'єкт не є "в" потоці, і поточний потік не обов'язково є потоком, який створив об'єкт.
Thomas Levesque

24

Якщо ви хочете змінити елемент керування, це потрібно зробити в потоці, в якому елемент управління був створений. Цей Invokeметод дозволяє виконувати методи у пов’язаному потоці (потоці, якому належить основний дескриптор вікна елемента керування).

Нижче зразок thread1 видає виняток, оскільки SetText1 намагається змінити textBox1.Text з іншого потоку. Але в thread2, Action у SetText2 виконується в потоці, в якому був створений TextBox

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"));
}

Мені дуже подобається такий підхід, він приховує природу делегатів, але це все одно приємний ярлик.
shytikov

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

Використання System.Action люди пропонують щодо інших відповідей працює лише на фреймворці 3.5+, для старих версій це працює ідеально
Suicide Platypus

2

На практиці це означає, що делегат гарантовано буде викликаний в основному потоці. Це важливо, оскільки у випадку елементів керування Windows, якщо ви не оновлюєте їх властивості в основному потоці, ви або не бачите змін, або елемент керування викликає виняток.

Шаблон:

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)переконайтеся, що ви викликаєте аргумент делегата до this.Invoke()основного потоку / створеного потоку.

Я можу сказати, що правило Thumb не має доступу до елементів керування формою, крім основного потоку.

Можливо, наступні рядки мають сенс для використання 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;
        }
    }

Існують ситуації, хоча ви створюєте потік Threadpool (тобто робочий потік), який буде працювати в основному потоці. Це не створить новий потік, оскільки основний потік доступний для обробки подальших інструкцій. Тож спочатку дослідіть, чи поточний запущений потік є основним потоком, this.InvokeRequiredякщо повертає true, поточний код працює на робочому потоці, тож зателефонуйте this.Invoke (d, новий об’єкт [] {текст});

в іншому випадку безпосередньо оновіть елемент керування інтерфейсом (тут ви гарантовано запускаєте код в основному потоці.)


1

Це означає, що делегат буде працювати в потоці інтерфейсу користувача, навіть якщо ви викликаєте цей метод із фонового робочого потоку або потоку пулу потоків. Елементи інтерфейсу мають спорідненість потоку - їм подобається спілкуватися безпосередньо з одним потоком: потоком інтерфейсу. Потік інтерфейсу визначається як потік, який створив екземпляр елемента керування, і, отже, пов'язаний з дескриптором вікна. Але все це - деталь реалізації.

Ключовим моментом є: ви б викликали цей метод із робочого потоку, щоб мати доступ до користувацького інтерфейсу (щоб змінити значення в мітці тощо) - оскільки вам заборонено робити це з будь-якого іншого потоку, окрім потоку інтерфейсу.


0

Делегат по суті вбудований Actionабо Func<T>. Ви можете оголосити делегата поза сферою дії методу, який ви запускаєте або використовуєте lambdaвираз ( =>); оскільки ви запускаєте делегат у методі, ви запускаєте його в потоці, який запускається для поточного вікна / програми, який виділений жирним шрифтом.

Приклад лямбда

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

0

Це означає, що переданий вами делегат виконується в потоці, який створив об’єкт Control (який є потоком інтерфейсу користувача).

Вам потрібно викликати цей метод, коли ваша програма багатопотокова, і ви хочете виконати певну операцію з інтерфейсом із потоку, відмінного від потоку інтерфейсу, тому що якщо ви просто спробуєте викликати метод на контролі з іншого потоку, ви отримаєте System.InvalidOperationException.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.