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

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

Ответов (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

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

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