我可以使异步组件显示为同步吗?

时间:2009-08-18 21:20:35

标签: c#

我目前正在尝试修复一个初始化问题,该问题源于所有子组件同步初始化的假设。

UI实例化具有自己的UI的类。它看起来像这样:

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的属性,因此当前构造函数通常不会正确初始化。将剩余的初始化放入helper_ReadyEvent似乎是显而易见的,但这会导致构造函数在对象准备好使用之前返回。由于使用ConfWizard对象的类假设一旦构造函数返回该对象完全可以使用,因此不希望过早返回。

不幸的是我无法更改HelperClass所以我需要屏蔽它的异步行为,以便可以同步使用ConfWizard类。

我尝试使用ManualResetEvent对象(在事件处理程序中调用Set)但是对WaitOne的调用是阻塞的,因此事件不会挂起应用程序。

有关如何在.NET1.1中实现此目的的任何想法吗?

更新 - 2009年8月21日
我今天有时间进行实验,这是我找到的。

WaitOne - 如果给定足够大的超时,则每次只需停止应用程序即可。不幸的是,超时需要至少5秒(比我要等待的时间长)。没有超时,它仍然挂起。调用set的事件根本不会发生。

睡觉 - 和WaitOne一样,只要有足够长的超时,它就会起作用。

线程 - 我不希望UI在初始化完成之前继续,因为初始化结果会改变UI的行为。但是,将HelperClass对象的初始化拆分为单独的线程并调用Thread.Join来暂停主线程。

因此问题的解决方案似乎是以正确的方式使用多个线程。

5 个答案:

答案 0 :(得分:1)

你破解它并在配置向导上添加一个只读属性,每当调用helper_ReadyEvent委托时,该属性都设置为true。然后,您可以轮询属性并在表单准备好后显示对话框。

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

或者,在初始化ConfigWizard之前,无法初始化辅助类?那么你可以只提供一个对通过类构造函数初始化为配置形式的辅助类的引用?鉴于此处的回复数量,我认为有很多方法可以完成任务。

答案 1 :(得分:0)

我不明白ManualResetEvent在你的情况下是如何失败的。如果您在HelperClass实例后创建一个Set实例并ReadyEvent,那么您需要做的就是在WaitOne的底部添加ConfWizard {1}}构造函数。是的,WaitOne会阻止,但这是行为(你的ConfWizard构造函数在一切准备好之前都没有返回)你想要的不是吗?

答案 2 :(得分:0)

我的第一个想法是,“使用等待句柄”,但正如你在帖子末尾所说的那样,这将不起作用,因为事件将尝试在UI线程上加注,但它被阻止,因为它正在等待UI线程。

(我认为这就是为什么它不起作用的原因。如果它在后台线程上引发 - 只需使用ManualResetEvent来通知它准备好的UI线程。)

另一种解决方案是让表单显示,即使辅助类没有准备就绪,并将辅助类上的所有操作重定向到在准备就绪时处理的操作队列:

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();
}

答案 3 :(得分:0)

  

我尝试使用ManualResetEvent对象(在事件处理程序中调用Set)但是对WaitOne的调用是阻塞的,因此事件不会挂起应用程序。

这必须意味着在ctor所在的同一个线程上调用ReadyEvent,或者HelperClass需要UI线程。这有点像泡菜 - 因为你无法真正延迟构造函数返回。

如果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();
      });
   }
}

否则 - 我认为您要么必须通过工厂异步创建,要么只处理HelperClass可能没有准备好的事实,并根据需要重新处理或禁用UI。

编辑:

  

[DoEvents]和VB6一样可怕吗?

对于many peopleyes。但是,IMO,它(像往常一样)取决于场景。由于可能的重入,你必须小心 - 如果你是因为窗口消息而运行,那么你可能需要防范它。

FWIW,DoEvents最常见的用途是重新绘制屏幕,​​因为长时间运行的操作系统会挂起UI。在那种情况下 - 是的,.NET为您提供了更好的处理线程的方法。但是,如果你只是想要产生UI线程(你做),我(and others)看到一些谨慎和谨慎放置的DoEvent调用没有问题。而且,坦率地说,我认为这是你选择中最不复杂的(当然还有重写HelperClass)。

答案 4 :(得分:-1)

为什么不挂钩

helper.StartUp();

到helper.ReadyEvent也是?