Windows Forms: есть ли способ дождаться завершения всех ожидающих вызовов элемента управления? - PullRequest
1 голос
/ 22 сентября 2011

Мне нужно сделать это, чтобы выйти из тупика. Мой элемент управления Windows Forms имеет ссылку на класс C ++ / CLI, который обёртывает собственный класс C ++. Нативный класс выполняет обратные вызовы к классу C ++ / CLI, который отображает их на события, обрабатываемые формой. Эти обратные вызовы вызываются из потока, который выполняется постоянно.

Когда я хочу избавиться от элемента управления, я отменяю регистрацию всех событий, чтобы нативный класс больше не мог перезванивать. Как только это будет сделано, я располагаю оболочкой C ++ / CLI, которая, в свою очередь, уничтожает собственный класс. В деструкторе нативного класса я сигнализирую завершению потока, используя событие Windows, и ожидаю завершения потока в течение неопределенного времени.

Однако, если поток находился в середине обратного вызова, когда начинается удаление, он может быть остановлен через Control.Invoke, и возникает тупик. Отсюда и заглавный вопрос. Это возможно? Если бы это было так, я мог бы поступить так:

  • Отменить регистрацию всех событий (нить больше не сможет перезванивать)
  • Дождитесь окончания всех ожидающих вызовов. 1011 *
  • Утилизировать оболочку C ++ / CLI
    • Уничтожить родной класс C ++
      • Сигнальная нить до конца
      • Ожидание завершения потока (невозможно заблокировать с помощью Invoke, так как все они завершены, и больше не могло быть запущено, поскольку события не были зарегистрированы)
  • Bliss

Я открыт для других предложений по решению этой проблемы

Ответы [ 2 ]

2 голосов
/ 23 сентября 2011

Вариант 1. Обработка ожидающих обратных вызовов через Application.DoEvents после удаления обработчиков событий, но перед деструктором. Обратите внимание, что в документации сказано, что это обрабатывает сообщения Windows и не упоминается, если это вызывает обработку вызовов.

Вариант 2: не блокировать поток событий: вызывать обратные вызовы формы через BeginInvoke. Обратите внимание, что вы можете получить события после уничтожения объекта C ++, поэтому добавьте дополнительную логику в обработчики событий, чтобы гарантировать, что объект C ++ не был уничтожен.

FYI, BeginInvoke и Invoke оба используют эту функцию. BeginInvoke устанавливает synchronous в ложь. Обратите внимание, что я немного взломал этот метод, чтобы удалить неинтересные части:

private object MarshaledInvoke(Control caller, Delegate method, object[] args, bool synchronous)
{
    ThreadMethodEntry entry = new ThreadMethodEntry(caller, method, args, synchronous, executionContext);
    lock (this.threadCallbackList)
    {
        if (threadCallbackMessage == 0)
            threadCallbackMessage = SafeNativeMethods.RegisterWindowMessage(Application.WindowMessagesVersion + "_ThreadCallbackMessage");
        this.threadCallbackList.Enqueue(entry);
    }
    UnsafeNativeMethods.PostMessage(new HandleRef(this, this.Handle), threadCallbackMessage, IntPtr.Zero, IntPtr.Zero);

    // BeginInvoke just takes this branch instead of waiting for completion
    if (!synchronous)
        return entry;
    if (!entry.IsCompleted)
        this.WaitForWaitHandle(entry.AsyncWaitHandle);
    if (entry.exception != null)
        throw entry.exception;
    return entry.retVal;
}
1 голос
/ 23 сентября 2011

Нет возможности ответить на событие закрытия формы и дождаться завершения потока.Существует неразрешимое состояние многопоточности.И очень хорошие шансы на тупик, DoEvents сломает его, но это слишком уродливо.

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

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

...