В WinForms, почему вы не можете обновить элементы управления пользовательского интерфейса из других потоков? - PullRequest
18 голосов
/ 14 августа 2008

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

Ответы [ 7 ]

24 голосов
/ 14 августа 2008

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

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

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

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

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

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

[Изменить]

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

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

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

10 голосов
/ 14 августа 2008

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

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

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

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

myControl.BeginInvoke(myControl.UpdateFunction);

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

7 голосов
/ 14 августа 2008

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


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


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

2 голосов
/ 14 августа 2008

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

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

Подробнее об отправке и отправке здесь .

1 голос
/ 14 августа 2008

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

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

1 голос
/ 14 августа 2008

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

Edit:

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

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

0 голосов
/ 13 октября 2008

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...