Вызов (делегат)


93

Кто-нибудь может объяснить это заявление, написанное по этой ссылке

Invoke(Delegate):

Выполняет указанный делегат в потоке, которому принадлежит базовый дескриптор окна элемента управления.

Может ли кто-нибудь объяснить, что это значит (особенно жирный), я не могу это четко понять


4
Ответ на этот вопрос связан со свойством Control.InvokeRequired - см. Msdn.microsoft.com/en-us/library/…
тире

Ответы:


131

Ответ на этот вопрос заключается в том, как работают элементы управления C #.

Элементы управления в Windows Forms привязаны к определенному потоку и не являются потокобезопасными. Следовательно, если вы вызываете метод элемента управления из другого потока, вы должны использовать один из методов вызова элемента управления для маршалинга вызова в соответствующий поток. Это свойство можно использовать, чтобы определить, нужно ли вызывать метод invoke, что может быть полезно, если вы не знаете, какой поток владеет элементом управления.

Из Control.InvokeRequired

Фактически, Invoke гарантирует, что вызываемый вами код выполняется в потоке, в котором «живет» элемент управления, эффективно предотвращая перекрестные исключения.

С исторической точки зрения в .Net 1.1 это действительно было разрешено. Это означало, что вы можете попробовать выполнить код в потоке «GUI» из любого фонового потока, и это в основном сработает. Иногда это просто приводило к завершению вашего приложения, потому что вы фактически прерывали поток графического интерфейса пользователя, пока он делал что-то еще. Это исключение Cross Threaded Exception - представьте, что вы пытаетесь обновить TextBox, пока графический интерфейс рисует что-то еще.

  • Какое действие имеет приоритет?
  • Возможно ли, чтобы и то и другое произошло одновременно?
  • Что происходит со всеми другими командами, которые необходимо запустить графическому интерфейсу?

Фактически вы прерываете очередь, что может иметь множество непредвиденных последствий. Invoke - это, по сути, «вежливый» способ поместить то, что вы хотите сделать, в эту очередь, и это правило применялось начиная с .Net 2.0 и далее с помощью брошенного InvalidOperationException .

Чтобы понять, что на самом деле происходит за кулисами и что подразумевается под «потоком графического интерфейса», полезно понять, что такое Message Pump или Message Loop.

На самом деле на этот вопрос уже дан ответ в вопросе « Что такое Message Pump », и его рекомендуется прочитать для понимания реального механизма, с которым вы связаны при взаимодействии с элементами управления.

Другое чтение, которое может оказаться полезным, включает:

Что случилось с 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 Required .

Также может быть интересна дальнейшая запись исторического события .


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идет речь, обязательно находится в текущей ветке, верно?
Кайл Делани

1
@KyleDelaney, объект не находится «в» потоке, и текущий поток не обязательно является потоком, создавшим объект.
Thomas Levesque

24

Если вы хотите изменить элемент управления, это необходимо сделать в потоке, в котором этот элемент управления был создан. Этот Invokeметод позволяет выполнять методы в связанном потоке (потоке, которому принадлежит базовый дескриптор окна элемента управления).

В приведенном ниже примере thread1 выдает исключение, потому что SetText1 пытается изменить textBox1.Text из другого потока. Но в thread2 действие в 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"));
}

Мне очень нравится этот подход, он скрывает характер делегатов, но в любом случае это хороший ярлык.
шитиков

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

Использование System.Action, которое люди предлагают в других ответах, работает только на framework 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.InvokeRequiredif возвращает true, текущий код выполняется в рабочем потоке, поэтому вызовите this.Invoke (d, new object [] {text});

иначе напрямую обновите элемент управления пользовательского интерфейса (здесь вы гарантированно запускаете код в основном потоке.)


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.