У меня есть сценарий. (Windows Forms, C #, .NET)
- Существует основная форма, которая содержит некоторый пользовательский элемент управления.
- Пользовательский элемент управления выполняет некоторую сложную операцию с данными, так что, если я напрямую вызываю
UserControl_Load
метод, пользовательский интерфейс перестает отвечать на запросы при выполнении метода загрузки. - Чтобы преодолеть это, я загружаю данные в другой поток (пытаясь как можно меньше изменить существующий код)
- Я использовал фоновый рабочий поток, который будет загружать данные и когда это будет сделано, уведомит приложение о том, что оно выполнило свою работу.
- Теперь появилась настоящая проблема. Весь пользовательский интерфейс (основная форма и ее дочерние элементы управления) был создан в основном основном потоке. В методе LOAD пользовательского контроля я выбираю данные, основанные на значениях некоторого элемента управления (например, текстового поля) в userControl.
Псевдокод будет выглядеть так:
КОД 1
UserContrl1_LoadDataMethod()
{
if (textbox1.text == "MyName") // This gives exception
{
//Load data corresponding to "MyName".
//Populate a globale variable List<string> which will be binded to grid at some later stage.
}
}
Это исключение было
Недопустимая операция между потоками: доступ к элементу управления из потока, отличного от потока, в котором он был создан.
Чтобы узнать больше об этом, я немного погуглил, и пришло предложение, например, с использованием следующего кода
Код 2
UserContrl1_LoadDataMethod()
{
if (InvokeRequired) // Line #1
{
this.Invoke(new MethodInvoker(UserContrl1_LoadDataMethod));
return;
}
if (textbox1.text == "MyName") // Now it wont give an exception
{
//Load data correspondin to "MyName"
//Populate a globale variable List<string> which will be binded to grid at some later stage
}
}
НО НО ... кажется, я вернулся к исходной точке. Приложение снова перестает отвечать на запросы. Кажется, это связано с выполнением строки # 1, если условие. Задача загрузки снова выполняется родительским потоком, а не третьим, который я породил.
Я не знаю, понял ли я это правильно или неправильно. Я новичок в потоке.
Как мне решить это, а также каков эффект выполнения строки # 1, если блок?
Ситуация такова : я хочу загрузить данные в глобальную переменную на основе значения элемента управления. Я не хочу менять значение элемента управления из дочернего потока. Я не собираюсь делать это когда-либо из детской ветки.
Так что доступ только к значению, так что соответствующие данные могут быть получены из базы данных.
источник
Ответы:
Согласно обновленному комментарию Prerak K (так как удалено):
Решение, которое вы хотите, должно выглядеть следующим образом:
Выполните серьезную обработку в отдельном потоке, прежде чем пытаться переключиться обратно на поток элемента управления. Например:
источник
Модель потоков в пользовательском интерфейсе
Пожалуйста, прочитайте Модель потоков в приложениях пользовательского интерфейса ( старая ссылка на VB здесь ), чтобы понять основные понятия. Ссылка переходит на страницу, которая описывает модель потоков WPF. Однако Windows Forms использует ту же идею.
Поток пользовательского интерфейса
Методы BeginInvoke и Invoke
Invoke
BeginInvoke
Кодовое решение
Читайте ответы на вопрос Как обновить графический интерфейс из другого потока в C #? , Для C # 5.0 и .NET 4.5 рекомендуемое решение здесь .
источник
Вы хотите только использовать
Invoke
илиBeginInvoke
для голого минимальной части работ , необходимых для изменения пользовательского интерфейса. Ваш "тяжелый" метод должен выполняться в другом потоке (например, черезBackgroundWorker
), но затем использоватьControl.Invoke
/Control.BeginInvoke
просто для обновления пользовательского интерфейса. Таким образом, ваш поток пользовательского интерфейса будет свободен для обработки событий пользовательского интерфейса и т. Д.См. Мою многопоточную статью для примера WinForms - хотя статья была написана до того, как
BackgroundWorker
появилась на сцене, и я боюсь, что я не обновил ее в этом отношении.BackgroundWorker
просто немного упрощает обратный вызовисточник
Я знаю, что уже слишком поздно. Однако даже сегодня, если у вас возникают проблемы с доступом к элементам управления несколькими потоками? Это самый короткий ответ до даты: P
Вот так я получаю доступ к любому элементу управления формы из потока.
источник
Invoke or BeginInvoke cannot be called on a control until the window handle has been created
. Я решил это здесьУ меня была эта проблема с
FileSystemWatcher
и обнаружил, что следующий код решил эту проблему:fsw.SynchronizingObject = this
Элемент управления затем использует текущий объект формы для обработки событий и, следовательно, будет в том же потоке.
источник
.SynchronizingObject = Me
Я считаю, что код проверки и вызова, который необходимо замусорить во всех методах, связанных с формами, слишком многословен и не нужен. Вот простой метод расширения, который позволяет полностью избавиться от него:
И тогда вы можете просто сделать это:
Нет больше возиться - просто.
источник
t
в этом случае будетtextbox1
- это передано в качестве аргументаЭлементы управления в .NET не являются потокобезопасными. Это означает, что вы не должны обращаться к элементу управления из потока, отличного от того, в котором он находится. Чтобы обойти это, вам нужно вызвать элемент управления, который пытается ваш второй пример.
Тем не менее, в вашем случае все, что вы сделали, это передали долгосрочный метод обратно в основной поток. Конечно, это не совсем то, что вы хотите сделать. Вам нужно немного переосмыслить, чтобы все, что вы делаете в главном потоке, устанавливало быстрое свойство здесь и там.
источник
Самое чистое (и правильное) решение для проблем с многопоточностью пользовательского интерфейса - это использование SynchronizationContext, см. Статью Синхронизация вызовов пользовательского интерфейса в статье о многопоточном приложении , это очень хорошо объясняется.
источник
Новый взгляд с использованием Async / Await и обратных вызовов. Вам нужна только одна строка кода, если вы сохраняете метод расширения в своем проекте.
Вы можете добавить другие вещи в метод Extension, например, обернуть его в оператор Try / Catch, что позволит вызывающей стороне сообщить ему, какой тип возвращать после завершения, обратный вызов исключительной ситуации для вызывающей стороны:
Добавление Try Catch, Auto Exception Logging и CallBack
источник
Это не рекомендуемый способ устранения этой ошибки, но вы можете быстро ее устранить, это сделает работу. Я предпочитаю это для прототипов или демонстраций. Добавить
в
Form1()
конструкторе.источник
Следуйте простейшему (на мой взгляд) способу изменения объектов из другого потока:
источник
Вам нужно взглянуть на пример Backgroundworker:
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx Особенно о том, как он взаимодействует со слоем пользовательского интерфейса. Судя по вашей публикации, это, кажется, отвечает вашим проблемам.
источник
Я нашел в этом необходимость, когда программировал контроллер приложения iOS-Phone monotouch в визуальной студии. Проект-образец winforms за пределами xamarin stuidio. Предпочитая максимально программировать в VS поверх студии xamarin, я хотел, чтобы контроллер был полностью отделен от каркаса телефона. Таким образом, реализация этого для других платформ, таких как Android и Windows Phone, будет намного проще для использования в будущем.
Я хотел найти решение, в котором графический интерфейс мог бы реагировать на события без бремени обработки кода переключения между потоками за каждым нажатием кнопки. По сути, пусть контроллер класса справится с этим, чтобы клиентский код был простым. Вы могли бы иметь много событий в GUI, где, как если бы вы могли обрабатывать это в одном месте в классе, было бы чище. Я не многопрофильный эксперт, дайте мне знать, если это неправильно.
Форма графического интерфейса не знает, что контроллер выполняет асинхронные задачи.
источник
Вот альтернативный способ, если объект, с которым вы работаете, не имеет
Это полезно, если вы работаете с основной формой в классе, отличном от основной формы, с объектом, который находится в главной форме, но не имеет InvokeRequired
Он работает так же, как и выше, но это другой подход, если у вас нет объекта с invokerequired, но есть доступ к MainForm
источник
В том же ключе, что и в предыдущих ответах, но очень короткое дополнение, позволяющее использовать все свойства элемента управления без исключения межпоточного вызова
Вспомогательный метод
Образец использования
источник
источник
Например, чтобы получить текст из элемента управления потока пользовательского интерфейса:
источник
Тот же вопрос: как обновлять графический интерфейс пользователя из другого потока в c
Два пути:
Верните значение в e.result и используйте его для установки значения текстового поля в событии backgroundWorker_RunWorkerCompleted
Объявите некоторую переменную для хранения таких значений в отдельном классе (который будет работать как держатель данных). Создайте статический экземпляр этого класса, и вы можете получить к нему доступ через любой поток.
Пример:
источник
Просто используйте это:
источник
Действие у; // объявлено внутри класса
label1.Invoke (у = () => Label1.Text = "текст");
источник
Простой и многократно используемый способ обойти эту проблему.
Метод расширения
Образец использования
источник
Существует два варианта операций с несколькими потоками.
а второй использовать
Control.InvokeRequired полезен только в том случае, если рабочие элементы управления унаследованы от класса Control, тогда как SynchronizationContext может использоваться где угодно. Некоторая полезная информация в виде следующих ссылок
Интерфейс Обновления Cross Thread | .Сеть
Интерфейс обновления Cross Thread с использованием SynchronizationContext | .Сеть
источник