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

Работая над существующим проектом, я должен использовать WinForms (не работая с ним некоторое время), и у вас возникла проблема синхронизации с потоком пользовательского интерфейса.

Дизайн, который мне нужно интегрировать с работами следующим образом: A BackgroundWorker получает Action в качестве параметра и выполняет его асинхронно. Действие, над которым я работаю, состоит из двух частей; базовый класс (содержащий бизнес-логику) и часть GUI, которая уведомляется ядром через события, если требуется запросить взаимодействие с пользователем.

Я добавил создание дескриптора в конструктор формы

if (!IsHandleCreated)
{
    //be sure to create the handle in the constructor
    //to allow synchronization with th GUI thread
    //when using Show() or ShowDialog()
    CreateHandle();
}

При этом работает следующий код:

private DialogResult ShowDialog(Form form)
{
    DialogResult dialogResult = DialogResult.None;
    Action action = delegate { dialogResult = form.ShowDialog(); };
    form.Invoke(action);
    return dialogResult;
}

В этом примере местоположение запуска было установлено на Windows по умолчанию.

Если я изменил его на:

Action action = delegate { dialogResult = form.ShowDialog(ParentWindow); };

Где ParentWindow - это экземпляр IWin32Window, а WindowStartupLocation - CenterParent. При вызове form.Invoke(action) я получаю исключение для перекрестных потоков.

Неверная операция поперечного потока: элемент управления "ActivationConfirmationForm" доступен из потока, отличного от потока, на котором он был создан.

Вопросы:

  • Почему существует исключение перекрестного потока только при настройке места запуска как CenterParent? И как мне избежать этого?
  • Почему form.InvokeRequired всегда false?

Оба, вероятно, связаны!?

[править] @Reniuz: Здесь ничего не пропало;) Вызов выполняется из прослушивателя, уведомляемого ядром

private static void OnActivationConfirmationRequired(DmsPackageConfiguratorCore sender,
ConfigurationActivationConfirmationEventArgs args)
{
    args.DoAbort = (ShowDialog(new ActivationConfirmationForm(args.Data)) == DialogResult.No);
}

Все в моем распоряжении находится в интерфейсе графического интерфейса

/// <summary>
/// Interface defining methods and properties used to show dialogs while performing package specific operations
/// </summary>
public interface IPackageConfiguratorGui
{
/// <summary>
/// Gets or sets the package configurator core.
/// </summary>
/// <value>The package configurator core.</value>
IPackageConfiguratorCore PackageConfiguratorCore { get; set; }

/// <summary>
/// Gets or sets the parent window.
/// </summary>
/// <value>The parent window.</value>
IWin32Window ParentWindow { get; set; }

/// <summary>
/// Gets the package identifier.
/// </summary>
/// <value>The package identifier.</value>
PackageIdentifier PackageIdentifier { get; }
}

Ответ 1

Видя форму. InvokeRequired at false - ядро ​​вашей проблемы. Вы знаете, что это должно быть правдой. Простое объяснение заключается в том, что объект формы, который передается вашему методу ShowDialog(), является неправильным объектом. Классической ошибкой является использование нового для создания экземпляра вместо использования существующего экземпляра объекта формы, того, на который пользователь смотрит и который был создан в основном потоке. Убедитесь, что код с резьбой имеет ссылку на этот объект формы, чтобы он мог передать правильную ссылку. Используйте Application.OpenForms [0], если вы не можете это исправить.

В общем, отделите резьбовой код от пользовательского интерфейса. Рабочий поток не имеет бизнес, отображающий диалог. Вы можете заставить его работать, но на практике это не работает. Диалоговое окно появляется без ожидания пользователя. Вероятно, что вероятность несчастного случая, пользователь может щелкнуть или нажать клавишу за долю секунды, прежде чем появится диалоговое окно. Отклонить диалог, даже не увидев его. Аналогично, взломать CreateHandle() нельзя в коде. Просто не запускайте поток, пока пользовательский интерфейс не будет готов. Сигнал в форме Загрузка события.

Ответ 2

ok У меня нет разрешения "комментировать", так как я новый пользователь, поэтому я просто использую это пространство для ответа.

вы создаете новый экземпляр формы ActivationConfirmationForm, в зависимости от того, какой поток вы используете, создание этой формы и выполнение ShowDialog выполняется в том же контексте потока, поскольку это верно, InvokeRequired (см. msdn) является очевидно, будет ложным, так как форма, к которой вы хотите получить доступ, создается в потоке, к которому вы обращаетесь. Не нужно использовать invoke/begininvoke и т.д. Вид того, о чем беспокоился @reniuz.

Ответ 3

Форма принадлежит другому потоку родительскому.

Похоже, что аргумент позиции WinForms CenterParent вызывает вызов объекта WinForms.Net, а не использует API Win32, чтобы найти позицию родительского окна из HWND, и этот вызов по перекрестным потокам является причиной исключения перекрестного потока.

Реальный ответ: рабочие потоки не должны иметь пользовательский интерфейс. Они должны дать результат, который указывает, что требуется вмешательство пользователя, а основной поток должен заботиться о взаимодействии с пользователем.

В противном случае не устанавливайте родительское окно для GUI рабочего потока. Он может (просто) быть работоспособным, если у вас есть только один рабочий поток, но он вызовет всевозможные путаницы, если у вас больше, чем ни одного.

Если вам абсолютно необходимо, используйте P/Invoke, чтобы найти текущую позицию окна родительского окна из Win32API и установить его явно.

Ответ 4

В качестве продолжения:
У меня теперь есть рабочее решение (диалог не был модальным без установки родителя). Я соглашусь не на то, чтобы начать диалог с рабочего потока, но в этом случае это требование. ParentForm теперь представляет собой форму, а не IWin32Window перед

Создайте диалог в графическом потоке:

private void OnActivationConfirmationRequired(DmsPackageConfiguratorCore sender, ConfigurationActivationConfirmationEventArgs args)
{
    //create the dialog in the graphical thread
    ActivationConfirmationForm dialog = null;
    Action createDialogInGuiThread = () => dialog = new ActivationConfirmationForm(args.Data);
    ParentForm.Invoke(createDialogInGuiThread);

    if (dialog != null)
    {
        args.DoAbort = (ShowDialog(dialog) == DialogResult.No);
    }
}

Вызов диалога из потока пользовательского интерфейса

private DialogResult ShowDialog(Form form)
{
    DialogResult dialogResult = DialogResult.None;

    //launch the form in the graphical htread (the one of the parent form)
    Action action = delegate { dialogResult = form.ShowDialog(ParentForm); };
    ParentForm.Invoke(action);

    return dialogResult;
}

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