Почему в WinForms вы не можете обновить элементы управления пользовательского интерфейса из других потоков?

StackOverflow https://stackoverflow.com/questions/10349

  •  08-06-2019
  •  | 
  •  

Вопрос

Я уверен, что для этого есть веская (или, по крайней мере, достойная) причина.Что это такое?

Это было полезно?

Решение

Потому что вы можете легко попасть в тупик (помимо других проблем).

Например, ваш вторичный поток может пытаться обновить элемент управления пользовательского интерфейса, но элемент управления пользовательского интерфейса будет ожидать освобождения ресурса, заблокированного вторичным потоком, поэтому оба потока в конечном итоге ждут завершения друг друга.Как отмечали другие, эта ситуация не уникальна для кода пользовательского интерфейса, но особенно распространена.

В других языках, таких как C++, вы можете попытаться сделать это (без выдачи исключений, как в WinForms), но ваше приложение может зависнуть и перестать отвечать на запросы в случае возникновения взаимоблокировки.

Кстати, вы можете легко сообщить потоку пользовательского интерфейса, что хотите обновить элемент управления, просто создайте делегат, а затем вызовите (асинхронный) метод BeginInvoke для этого элемента управления, передав ему свой делегат.Например.

myControl.BeginInvoke(myControl.UpdateFunction);

Это эквивалентно выполнению PostMessage C++/MFC из рабочего потока.

Другие советы

Я думаю, что это блестящий вопрос - и я думаю, что есть лучший ответ.

Конечно, единственная причина в том, что в рамках есть что-то, что не очень безопасно.

Это «что-то» — почти каждый член экземпляра каждого элемента управления в System.Windows.Forms.

Документация MSDN для многих элементов управления в System.Windows.Forms, если не для всех, скажем «Любые общедоступные статические (общие в Visual Basic) члены этого типа являются потокобезопасными.Потокобезопасность любых членов экземпляра не гарантируется».

Это означает, что члены экземпляра, такие как TextBox.Text {get; set;} не реентерабельный.

Обеспечение потокобезопасности каждого из этих членов экземпляра может привести к большим накладным расходам, которые не нужны большинству приложений.Вместо этого разработчики платформы .Net решили, и я думаю правильно, что бремя синхронизации доступа к элементам управления формами из нескольких потоков должно быть возложено на программиста.

[Редактировать]

Хотя этот вопрос спрашивает только «почему», вот ссылка на статью, объясняющую «как»:

Как:Выполнение потокобезопасных вызовов элементов управления Windows Forms в MSDN

http://msdn.microsoft.com/en-us/library/ms171728.aspx

Хотя это звучит разумно, ответ Джона неверен.На самом деле, даже при использовании Invoke вы все равно не застрахованы от тупиковых ситуаций.При работе с событиями, запускаемыми в фоновом потоке, использование Invoke может даже привести к этой проблеме.


Настоящая причина больше связана с условиями гонки и уходит корнями в древние времена Win32.Я не могу объяснить здесь детали, ключевые слова — это насосы сообщений, события WM_PAINT и тонкие различия между «SEND» и «POST».


Дополнительную информацию можно найти здесь. здесь и здесь.

Еще в 1.0/1.1 во время отладки не возникало никаких исключений, вместо этого вы получали периодический сценарий зависания во время выполнения.Хороший!:) Поэтому с 2.0 они заставили этот сценарий сделать исключение и вполне справедливо.

Фактической причиной этого, вероятно, является (как утверждает Адам Хейл) какая-то проблема параллелизма/блокировки.Обратите внимание, что обычный API .NET (например, TextBox.Text = "Hello";) оборачивает команды SEND (требующие немедленных действий), которые могут создавать проблемы, если они выполняются в отдельном потоке, отличном от того, который выполняет обновление.При использовании Invoke/BeginInvoke вместо этого используется POST, который ставит действие в очередь.

Дополнительная информация о SEND и POST здесь.

Это сделано для того, чтобы две вещи не пытались обновить элемент управления одновременно.(Это может произойти, если процессор переключается на другой поток в середине записи/чтения). Той и та же причина, по которой вам необходимо использовать мутекс (или какую -то другую синхронизацию) при доступе к общим переменным между несколькими потоками.

Редактировать:

На других языках, таких как C ++, вы можете попытаться сделать это (без исключения, которое будет брошено в Winforms), но вы в конечном итоге узнаете трудный путь!

Ах да... Я переключаюсь между C/C++ и C# и поэтому был немного более общим, чем должен был, извините...Он прав, ты может сделайте это на C/C++, но это аукнется вам!

Также может возникнуть необходимость реализовать синхронизацию внутри функций обновления, чувствительных к одновременному вызову.Выполнение этого для элементов пользовательского интерфейса было бы дорогостоящим как на уровне приложения, так и на уровне ОС, а также было бы совершенно избыточным для подавляющего большинства кода.

Некоторые API предоставляют способ изменить текущего владельца потока в системе, чтобы вы могли временно (или постоянно) обновлять системы из других потоков, не прибегая к межпотоковому обмену данными.

Хм, я не совсем уверен, но думаю, что когда у нас есть элементы управления прогрессом, такие как индикаторы ожидания, индикаторы выполнения, мы можем обновлять их значения из другого потока, и все работает отлично, без каких-либо сбоев.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top