Подтвердить что ты не робот

Вам нужно второе (и третье) мнение о моем исправлении для этого состояния гонки Winforms

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

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

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

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

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

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

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

Я знаком с различными подходами, в том числе с использованием события Form 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
   }

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

Любые комментарии? Я что-то пропустил?

EDIT: Я не прошу альтернатив. Не спрашивая, как использовать NLog или Log4net и т.д., Если бы я был, я бы написал страницу обо всех ограничениях для клиента в этом приложении и т.д.

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

Ответ 3

Вы всегда можете проверить свойство 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

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

Ответ 4

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

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

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