Можно ли сделать так, чтобы асинхронные компоненты выглядели синхронно?

2

В настоящее время я пытаюсь исправить проблему инициализации, которая проистекает из предположения, что все подкомпоненты инициализируются синхронно.

Пользовательский интерфейс создает экземпляр класса с собственным пользовательским интерфейсом. Это выглядит примерно так:

ConfWizard cf = new ConfWizard();
cf.ShowDialog();

Проблема заключается в том, что класс ConfWizard использует другой класс, который инициализирует асинхронно, но должен быть готов, прежде чем ShowDialog вызывается для правильного функционирования. Код ConfWizard выглядит примерно так:

public ConfWizard()
{
    helper = new HelperClass
    helper.ReadyEvent += new HelperClass.ReadyEventHandler(this.helper_ReadyEvent)
    helper.StartUp();
    // Do more initialization using properties of hc
}

private helper_ReadyEvent()
{
    //HelperClass is ready to use
}

Поскольку свойства помощника не могут быть установлены до момента, когда ReadyEvent будет поднят, текущий конструктор обычно не инициализируется правильно. Может показаться очевидным положить оставшуюся инициализацию в helper_ReadyEvent, но это приведет к возврату конструктора до того, как объект будет готов к использованию. Поскольку классы, использующие объект ConfWizard, предполагают, что когда конструктор вернет объект, он полностью готов к использованию, преждевременное возвращение нежелательно.

К сожалению, я не могу изменить HelperClass, поэтому мне нужно замаскировать его асинхронное поведение, чтобы класс ConfWizard можно было использовать синхронно.

Я попытался использовать объект ManualResetEvent (вызывая Set в обработчике событий), но вызовы WaitOne блокируются, и поэтому событие не обрабатывается приложением приложения.

Любые идеи о том, как достичь этого в .NET1.1?

ОБНОВЛЕНИЕ - 21 августа 2009 г.
У меня было время экспериментировать сегодня и здесь, что я нашел.

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

Спящий - такой же, как WaitOne, в котором с достаточно длинным таймаутом он будет работать.

Threading - я не хочу, чтобы интерфейс продолжался до тех пор, пока инициализация не будет выполнена, потому что поведение пользовательского интерфейса изменяется по результатам инициализации. Однако разделение инициализации объекта HelperClass на отдельный поток и вызов Thread.Join для приостановки работы основного потока.

Таким образом, решение проблемы, по-видимому, использует несколько потоков правильно.

Теги:

5 ответов

1

Вы взламываете его и добавляете свойство только для чтения в мастере конфигурации, для которого установлено значение true, когда вызывается делегат helper_ReadyEvent. Затем вы можете опросить свойство и показать диалог, как только форма будет готова.

ConfigWizard wiz = new ConfigWizard();
while (!wiz.Ready) System.Threading.Thread.Sleep(2000);
wiz.ShowDialog();

Или вы не могли инициализировать класс-помощник до инициализации ConfigWizard? Тогда вы могли бы просто предоставить ссылку на вспомогательный класс, который был инициализирован в конфигурационную форму через конструктор классов? Учитывая количество ответов здесь, мне кажется, есть много способов, которыми вы могли бы выполнить задачу.

0

Я попытался использовать объект ManualResetEvent (вызывая Set в обработчике событий), но вызовы WaitOne блокируются, и поэтому событие не обрабатывается приложением приложения.

Это должно означать, что либо ReadyEvent вызывается в том же потоке, что и ctor, либо что HelperClass нуждается в потоке пользовательского интерфейса. Это немного рассол, так как вы не можете задержать конструктор.

Если HelperClass просто нужно обработать некоторое оконное сообщение, вы можете вставить Application.DoEvents.

class ConfWizard {
    private ManualResetEvent _init = new ManualResetEvent(false);

    public ConfWizard() {
       var h = new HelperClass();
       h.ReadyEvent += this.helper_ReadyEvent;
       h.StartUp();

       do {
          Application.DoEvents();
       } while (!_init.WaitOne(1000));
   }    

   private void helper_ReadyEvent() {
       _init.Set();
   }
}

class HelperClass {
   public event Action ReadyEvent;

   public void StartUp() {
      ThreadPool.QueueUserWorkItem(s => {
         Thread.Sleep(10000);
         var e = this.ReadyEvent;
         if (e != null) e();
      });
   }
}

В противном случае - я думаю, вам придется либо асинхронно создавать через factory, либо просто иметь дело с тем фактом, что HelperClass может быть не готов и переработать или отключить пользовательский интерфейс, если это необходимо.

Edit:

Есть ли [DoEvents] так же страшно, как в VB6?

Для many люди, да. Но, ИМО, это (как обычно) зависит от сценария. Вы должны быть осторожны с ним из-за возможного повторного входа - если вы работаете в результате оконного сообщения, тогда вам, возможно, придется защититься от этого.

FWIW, наиболее распространенное использование DoEvents - это перерисовать экран из-за длительной работы, которая в противном случае зависала бы от пользовательского интерфейса. В этом случае - да,.NET дает вам много лучших способов обращения с потоками. Но если вы просто хотите получить поток пользовательского интерфейса (который вы делаете), я (и другие) не вижу проблем с некоторыми осторожно и осторожно размещенные вызовы DoEvent. И, честно говоря, я думаю, что это наименее сложный из ваших вариантов (без перезаписи HelperClass, конечно).

  • 0
    Я думал о чем-то вроде DoEvents, но подумал, что это кусок VB6, который, вероятно, не попал в .NET, и я даже не искал помощи для этого. Это так же страшно, как и в VB6?
0

Я не понимаю, как ManualResetEvent не работает в вашем случае. Если вы создадите один из них после создания экземпляра HelperClass и Set в своем ReadyEvent, то все, что вам нужно сделать, это добавить WaitOne в нижней части вашего конструктора ConfWizard. Да, WaitOne будет блокировать, но это поведение (что ваш конструктор ConfWizard не возвращается, пока все не будет готово), вы хотите, не так ли?

  • 0
    Блокирующее поведение - это то, что я хочу. К сожалению, если вы вызываете WaitOne в главном потоке, он также блокирует рассылку сообщений, препятствуя обработке ReadyEvent. Это означает, что Set никогда не вызывается и приложение зависает.
  • 0
    Обязательно ли инициализировать ConfWizard в потоке пользовательского интерфейса? Не можете ли вы делегировать инициализацию фоновому потоку, чтобы ваш поток пользовательского интерфейса мог продолжать выполнять задачи, связанные с пользовательским интерфейсом?
0

Моей первой мыслью было: "Использовать дескриптор ожидания", но, как вы сказали в конце своего сообщения, это не будет работать, поскольку событие будет пытаться поднять поток пользовательского интерфейса, но его заблокировать, пока он ждет поток пользовательского интерфейса.

(Я предполагаю, что это не работает. Если он поднимается на фоновом потоке - просто используйте ManualResetEvent, чтобы сигнализировать о том, что поток пользовательского интерфейса он готов.)

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

private Queue actions = new Queue();

public void DoSomethingToHelper()
{
   if(!helperClass.IsReady())
   {
      Action work = new Action(DoSomethingToComponent);
   }
   else
   {
       // Real work here.
   }
}

Затем, когда он готов, вы проходите и обрабатываете все действия:

private helper_ReadyEvent()
{
    foreach (Action action in actions)
    {
        action.Invoke();
    }
    actions.Clear();
}
-1

почему вы не подключаете

helper.StartUp();

к helper.ReadyEvent также?

  • 1
    Wha? Это либо не имеет смысла, либо мой мозг сегодня полностью прожарен ....

Ещё вопросы

Сообщество Overcoder
Наверх
Меню