Если я правильно понимаю ваш вопрос, вы бы хотели, чтобы фреймворк (или компилятор, или какой-то другой элемент технологии) включал Invoke / BeginInvoke / EndInvoke вокруг всех открытых членов объекта UI, чтобы сделать его потокобезопасным. Проблема в том, что это само по себе не сделает ваш поток кода безопасным. Вам все равно придется использовать BeginInvoke и другие механизмы синхронизации очень часто. (См. Эту замечательную статью о безопасности потоков в блоге Эрика Липперта )
Представьте, что вы пишете код как
if (myListBox.SelectedItem != null)
{
...
myLabel.Text = myListBox.SelectedItem.Text;
...
}
Если фреймворк или компилятор обернут каждый доступ к SelectedItem
и вызов Delete
в вызове BeginInvoke / Invoke, этот не будет безопасным для потоков. Существует потенциальное состояние гонки, если SelectedItem
не равно нулю, когда вычисляется условие if, но другой поток устанавливает его в null до завершения блока then. Вероятно, все предложение if-then-else-должно быть заключено в вызов BeginInvoke, но как это должно знать компилятор?
Теперь вы можете сказать «но это верно для всех общих изменяемых объектов, я просто добавлю блокировки». Но это довольно опасно. Представьте, что вы сделали что-то вроде:
// in method A
lock (myListBoxLock)
{
// do something with myListBox that secretly calls Invoke or EndInvoke
}
// in method B
lock (myListBoxLock)
{
// do something else with myListBox that secretly calls Invoke or EndInvoke
}
Это приведет к тупику: метод A вызывается в фоновом потоке. Он получает блокировку, вызывает Invoke. Invoke ожидает ответа из очереди сообщений потока пользовательского интерфейса. В то же время метод B выполняется в основном потоке (например, в Button.Click-Handler). Другой поток содержит myListBoxLock
, поэтому он не может войти в блокировку - теперь оба потока ждут друг друга, и ни один не может добиться прогресса.
Найти и избежать таких потоковых ошибок достаточно сложно, как есть, но, по крайней мере, теперь вы можете увидеть , что вы вызываете Invoke и что это блокирующий вызов. Если какое-либо свойство может молча блокировать, такие ошибки будут далеко труднее найти.
Мораль: нить сложная. Взаимодействие между потоками и пользовательским интерфейсом еще сложнее, поскольку существует только одна общая очередь сообщений. И, к сожалению, ни наши компиляторы, ни наши фреймворки не настолько умны, чтобы «просто заставить его работать правильно».