Ответов (7)7
Потому что вы легко можете попасть в тупик (помимо прочего).
Например, ваш вторичный поток может пытаться обновить элемент управления пользовательского интерфейса, но элемент управления пользовательского интерфейса будет ожидать освобождения ресурса, заблокированного второстепенным потоком, поэтому оба потока в конечном итоге ожидают завершения друг друга. Как отмечали другие, эта ситуация не уникальна для кода пользовательского интерфейса, но особенно распространена.
На других языках, таких как C++, вы можете попробовать и сделать это (без создания исключения, как в WinForms), но ваше приложение может зависнуть и перестать отвечать, если возникнет тупик.
Между прочим, вы можете легко сообщить потоку пользовательского интерфейса, что хотите обновить элемент управления, просто создайте делегат, а затем вызовите (асинхронный) метод BeginInvoke для этого элемента управления, передав ему свой делегат. Например
myControl.BeginInvoke(myControl.UpdateFunction);
Это эквивалентно выполнению C++ / MFC PostMessage из рабочего потока.
В версиях 1.0 / 1.1 во время отладки не возникало никаких исключений, вместо этого вы получали сценарий прерывистого зависания во время выполнения. Отлично! :) Поэтому в версии 2.0 они сделали этот сценарий исключением, и это правильно.
Фактическая причина этого, вероятно, (как утверждает Адам Хейл) в некоторой проблеме параллелизма / блокировки. Обратите внимание, что обычный .NET api (например, TextBox.Text = "Hello";) обертывает команды SEND (которые требуют немедленного действия), которые могут создавать проблемы, если они выполняются в отдельном потоке от того, который выполняет обновление. Использование Invoke / BeginInvoke вместо этого использует POST, который ставит действие в очередь.
Больше информации о SEND и POST здесь .
Это сделано для того, чтобы у вас не было двух вещей, пытающихся обновить элемент управления одновременно. (Это может произойти, если ЦП переключается на другой поток в середине записи / чтения). По той же причине вам нужно использовать мьютексы (или другую синхронизацию) при доступе к общим переменным между несколькими потоками.
Редактировать:
На других языках, таких как C++, вы можете попробовать и сделать это (без исключения, как в WinForms), но в конечном итоге вы узнаете трудный путь!
Ах да ... Я переключаюсь между C/C++ и C# и поэтому был немного более общим, чем должен был, извините ... Он прав, вы можете сделать это на C/C++, но он вернется к укусить вас!
Также потребуется реализовать синхронизацию в функциях обновления, которые чувствительны к одновременному вызову. Выполнение этого для элементов пользовательского интерфейса будет дорогостоящим как на уровне приложения, так и на уровне ОС и будет полностью избыточным для подавляющего большинства кода.
Некоторые API-интерфейсы предоставляют способ изменить текущее владение потоком системы, чтобы вы могли временно (или постоянно) обновлять системы из других потоков, не прибегая к межпотоковому взаимодействию.
Хотя это звучит разумно, ответ Джонса неверен. Фактически, даже при использовании Invoke вы по-прежнему небезопасно не попадете в тупиковые ситуации. При работе с событиями, запущенными в фоновом потоке, использование Invoke может даже привести к этой проблеме.
Настоящая причина больше связана с условиями гонки и уходит корнями в древние времена Win32. Я не могу объяснить здесь детали, ключевые слова - это перекачки сообщений, события WM_PAINT и тонкие различия между «SEND» и «POST».
Я думаю, что это блестящий вопрос - и я думаю, что на него нужен лучший ответ.
Конечно, единственная причина в том, что где-то в структуре есть что-то не очень ориентированное на многопотоковое исполнение.
Это «что-то» - это почти каждый член экземпляра каждого элемента управления в System.Windows.Forms.
В документации MSDN для многих элементов управления в System.Windows.Forms, если не для всех, говорится: «Любые общедоступные статические (общие в Visual Basic) члены этого типа являются потокобезопасными. Ни для кого из членов экземпляра не гарантируется потокобезопасность».
Это означает, что члены экземпляра, например TextBox.Text {get; set;}
, не реентерабельны .
Обеспечение потокобезопасности каждого из этих членов экземпляра может привести к большим накладным расходам, которые не нужны большинству приложений. Вместо этого разработчики инфраструктуры .Net решили, и я считаю правильно, что бремя синхронизации доступа к элементам управления форм из нескольких потоков должно быть возложено на программиста.
[Редактировать]
Хотя этот вопрос спрашивает только «почему», вот ссылка на статью, в которой объясняется «как»:
Как выполнить потокобезопасные вызовы элементов управления Windows Forms в MSDN