Почему в WinForms вы не можете обновить элементы управления пользовательского интерфейса из других потоков?
-
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
Хотя это звучит разумно, ответ Джона неверен.На самом деле, даже при использовании 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 предоставляют способ изменить текущего владельца потока в системе, чтобы вы могли временно (или постоянно) обновлять системы из других потоков, не прибегая к межпотоковому обмену данными.
Хм, я не совсем уверен, но думаю, что когда у нас есть элементы управления прогрессом, такие как индикаторы ожидания, индикаторы выполнения, мы можем обновлять их значения из другого потока, и все работает отлично, без каких-либо сбоев.