Ответ на этот вопрос заключается в том, как работают элементы управления 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 состоит в том, что только поток, создавший элемент управления, может получить доступ и / или изменить его содержимое (за исключением нескольких задокументированных исключений). Попробуйте сделать это из любого другого потока, и вы получите непредсказуемое поведение: от тупика до исключений и наполовину обновленного пользовательского интерфейса. Правильный способ обновить элемент управления из другого потока - отправить соответствующее сообщение в очередь сообщений приложения. Когда насос сообщений приступает к выполнению этого сообщения, элемент управления обновляется в том же потоке, который его создал (помните, насос сообщений работает в основном потоке).
и, для более подробного обзора кода с репрезентативной выборкой:
Недействительные межпоточные операции
public delegate void ControlStringConsumer(Control control, string text);
public void SetText(Control control, string text) {
if (control.InvokeRequired) {
control.Invoke(new ControlStringConsumer(SetText), new object[]{control, text});
} else {
control.Text=text;
}
}
Когда вы оцените InvokeRequired, вы можете рассмотреть возможность использования метода расширения для упаковки этих вызовов. Это умело рассматривается в вопросе о переполнении стека. Очистка кода с пометкой Invoke Required .
Также может быть интересна дальнейшая запись исторического события .