Странные ошибки пользовательского интерфейса при многопоточности

StackOverflow https://stackoverflow.com/questions/200314

Вопрос

Я пишу приложение WinForms, которое имеет два режима:консоль или графический интерфейс.Три проекта в одном решении: один для консольного приложения, один для форм пользовательского интерфейса и третий для хранения логики, которую оба интерфейса также будут соединять.Приложение Консоль работает абсолютно гладко.

Модель, которая хранит выбор пользователя, имеет IList<T> где T — локальный объект, Step, который реализует INotifyPropertyChanged, поэтому в пользовательском интерфейсе он монтируется в DataGridView.Во время выполнения все нормально, на экране отображается исходное состояние объектов.

Каждый из Step объекты — задача, которая выполняется по очереди;некоторые свойства изменятся, отражаясь обратно в IList и передаваясь в DataGridView.

Это действие в версиях пользовательского интерфейса выполняется путем создания BackgroundWorker, возвращающего события в пользовательский интерфейс.А Step делает это и генерирует StepResult объект, который представляет собой перечислимый тип, указывающий результат (например,Running, NotRun, OK, NotOK, Caveat) и строку, обозначающую сообщение (поскольку шаг был выполнен, но не совсем так, как ожидалось, т.е.с оговоркой).Обычно действия включают взаимодействие с базой данных, но в режиме отладки я генерирую результат случайным образом.

Если сообщение пустое, проблем никогда не возникнет, но если я сгенерирую такой ответ:

StepResult returnvalue = new StepResult(stat, "completed with caveat")

Я получаю сообщение об ошибке, сообщающее, что доступ к DataGridView осуществлялся из потока, отличного от потока, в котором он был создан.(Я передаю это через собственный обработчик, который должен обрабатывать вызов, когда это необходимо - может быть, это не так?)

Затем, если я сгенерирую уникальный ответ, например.используя случайное число r:

StepResult returnvalue = new StepResult(stat, r.ToString());

действия выполняются без проблем, числа четко записываются в DataGridView.

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

Это было полезно?

Решение

Поскольку вы выполняете привязку пользовательского интерфейса через подписку на события, вам может быть это полезно;это пример, который я написал некоторое время назад и показывает, как создавать подклассы BindingList<T> чтобы уведомления автоматически направлялись в поток пользовательского интерфейса.

Если нет контекста синхронизации (т.консольный режим), затем он возвращается к простому прямому вызову, поэтому накладные расходы отсутствуют.При работе в потоке пользовательского интерфейса обратите внимание, что по существу используется Control.Invoke, который сам просто запускает делегат напрямую, если он находится в потоке пользовательского интерфейса.Таким образом, любой переключатель доступен только в том случае, если данные редактируются из потока, не связанного с пользовательским интерфейсом, — это то, что мы хотим ;-p

Другие советы

Вы сами ответили на свой вопрос:-

Я получаю сообщение об ошибке, сообщающее, что доступ к DataGridView осуществлялся из потока, отличного от потока, в котором он был создан.

WinForms настаивает на том, чтобы все действия, выполняемые с формами и элементами управления, выполнялись в контексте потока, в котором была создана форма.Причина этого сложна, но она во многом связана с базовым API Win32.Подробности смотрите в различных записях на Старая новая вещь блог.

Что вам нужно сделать, так это использовать методы InvokeRequired и Invoke, чтобы гарантировать, что доступ к элементам управления всегда осуществляется из одного и того же потока (псевдокод):

object Form.SomeFunction (args)
{
  if (InvokeRequired)
  {
    return Invoke (new delegate (Form.Somefunction), args);
  }
  else
  {
    return result_of_some_action;
  }
}

У меня была такая же проблема раньше.Возможно, эта статья, которую я опубликовал об этом, может помочь.

http://cyberkruz.vox.com/library/post/net-problem-async-and-windows-forms.html

Я нашел эту статью - "Обновление IBindingList из другого потока" - что указало пальцем на BindingList -

Поскольку BindingList не настроен для асинхронных операций, вам необходимо обновить BindingList из того же потока, в котором он управлялся.

Явная передача родительской формы как ISynchronizeInvoke объект и создание оболочки для BindingList<T> сделал свое дело.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top