Нужно второе (и третье) мнение о моем исправлении для этого условия гонки Winforms - PullRequest
12 голосов
/ 23 июля 2011

Есть сотни примеров в блогах и т. Д. О том, как реализовать фонового работника, который регистрирует или присваивает статус элементу переднего плана GUI.Большинство из них включают подход для обработки состояния гонки, которое существует между порождением рабочего потока и созданием диалогового окна переднего плана с ShowDialog ().Однако мне пришло в голову, что простой подход состоит в том, чтобы принудительно создать дескриптор в конструкторе формы, чтобы поток не мог инициировать вызов Invoke / BeginInvoke в форме до создания его дескриптора.

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

Предположим также, что мы не хотим, чтобы NLog или какой-либо другой тяжелый фреймворк делал что-то подобное.простой и легкий.

Окно моего регистратора открывается с помощью ShowDialog () потоком переднего плана, но только после запуска фонового рабочего потока.Рабочий поток вызывает logger.Log (), который сам использует logForm.BeginInvoke () для правильного обновления элемента управления журналом в потоке переднего плана.

  public override void Log(string s)
  {
     form.BeginInvoke(logDelegate, s);
  }

Где logDelegate - это простая оболочка вокруг "form.Log () "или какой-либо другой код, который может обновить индикатор выполнения.

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

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

http://msdn.microsoft.com/en-us/library/system.windows.forms.control.handle(v=vs.71).aspx

MSDN говорит: «Если дескриптор еще не создан, ссылка на это свойство заставит его создать».

Таким образом, мой регистратор переносит форму, а его конструктор делает:

   public SimpleProgressDialog() {
       var h = form.Handle; // dereference the handle
   }

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

Есть комментарии?Я что-то упускаю?

РЕДАКТИРОВАТЬ: я не прошу альтернативы.Не спрашивая, как использовать NLog или Log4net и т. Д., Если бы я был, я бы написал страницу обо всех клиентских ограничениях в этом приложении и т. Д.

По количеству голосов было многодругие люди, которые хотели бы знать ответ тоже.

Ответы [ 4 ]

3 голосов
/ 27 июля 2011

Если вы обеспокоены тем, что ссылка Control.Handle зависит от побочного эффекта для создания дескриптора, вы можете просто вызвать Control.CreateControl() для его создания.Однако ссылка на свойство имеет то преимущество, что не инициализирует его, если оно уже существует.

Что касается того, безопасно ли это или нет, предполагая, что дескриптор создан, вы правы: если вы создаете дескриптор до появленияфоновое задание в том же потоке, вы избежите условия гонки.

3 голосов
/ 23 июля 2011

Мои два цента: на самом деле нет необходимости форсировать раннее создание дескриптора, если каркас журналирования просто поддерживает буфер не отображаемых записей журнала, пока дескриптор не был создан. Это может быть реализовано как Queue или многое другое. Отказ от порядка создания дескрипторов в .NET делает меня брезгливым

Я думаю, что единственная опасность - снижение производительности. Создание ручки откладывается в winforms, чтобы ускорить процесс. Тем не менее, поскольку это звучит так, как будто это одноразовая операция, это не звучит дорого, поэтому я думаю, что ваш подход хорош.

2 голосов
/ 27 июля 2011

Вы всегда можете проверить свойство IsHandleCreated вашей формы, чтобы увидеть, был ли уже создан дескриптор; Однако есть некоторые предостережения. Я был в том же месте, что и вы, где элементы управления winforms создаются / уничтожаются динамически, и происходит много многопоточности. Шаблон, который мы использовали, был примерно таким:

private void SomeEventHandler(object sender, EventArgs e) // called from a bg thread
{
    MethodInvoker ivk = delegate
    {
        if(this.IsDisposed)
            return; // bail out!  Run away!

        // maybe look for queued stuff if it exists?

        // the code to run on the UI thread
    };

    if(this.IsDisposed)
        return; // run away!  killer rabbits with pointy teeth!

    if(!this.IsHandleCreated) // handle not built yet, do something in the meantime
        DoSomethingToQueueTheCall(ivk);
    else
        this.BeginInvoke(ivk);
}

Большой урок здесь - ожидать kaboom, если вы попытаетесь взаимодействовать с вашей формой после ее удаления. Не полагайтесь на InvokeRequired, так как он вернет false на любой поток , если дескриптор элемента управления еще не создан. Также не полагайтесь исключительно на IsHandleCreated, так как он вернет false после удаления элемента управления.

По сути, у вас есть три флага, состояние которых скажет вам, что вам нужно знать о состоянии инициализации элемента управления и о том, находитесь ли вы в BG-потоке относительно элемента управления.

Элемент управления может находиться в одном из трех состояний инициализации:

  • Неинициализирован, дескриптор еще не создан
    • InvokeRequired возвращает false на каждый поток
    • IsHandleCreated возвращает false
    • IsDisposed возвращает false
  • Инициализирован, готов, активен
    • InvokeRequired делает то, что говорят документы
    • IsHandleCreated возвращает true
    • IsDisposed возвращает false
  • Выбытие
    • InvokeRequired возвращает false на каждый поток
    • IsHandleCreated возвращает false
    • IsDisposed возвращает true

Надеюсь, это поможет.

1 голос
/ 26 июля 2011

Поскольку вы создаете окно в вызывающем потоке, вы можете получить тупики. Если в потоке, который создает окно, нет обработчика сообщений, выполняющего ваш BeginInvoke, добавит ваш вызов делегата в очередь сообщений, которая никогда не будет очищена, если у вас нет Application.Run () в том же потоке, который будет обрабатывать сообщения окна ,

Также очень медленно отправлять сообщения вокруг окна для каждого сообщения журнала. Гораздо лучше иметь потребительскую модель производителя, в которой ваш поток регистрации добавляет сообщение в очередь , которая очищается другим потоком. Единственный раз, когда вам нужно заблокировать, это когда вы ставите в очередь или удаляете сообщение из очереди. Поток потребителя может ожидать события с тайм-аутом, чтобы начать обработку следующего сообщения, когда либо было сообщено событие, либо истекло время ожидания (например, 100 мс).

Очередь потоковой безопасной блокировки можно найти здесь .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...