简体   繁体   中英

Can I make asynchronous components appear synchronous?

I'm currently trying to fix an initialization problem that stems from the assumption that all sub-components initialize synchronously.

The UI instantiates a class that has its own UI. It looks something like this:

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

The trouble is that the ConfWizard class makes use of another class that initializes asynchronously but must be ready before ShowDialog is called for correct functioning. The ConfWizard code looks something like:

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
}

Since the properties of helper may not be set until just before the ReadyEvent is raised, the current constructor generally does not initialize correctly. It may seem obvious to put the remaining initialization into helper_ReadyEvent but that would result in the constructor returning prior to the object being ready for use. Since the classes using the ConfWizard object assume once the constructor returns the object is fully ready for use, returning prematurely is not desirable.

Unfortunately I can't change the HelperClass so somehow I need to mask its asynchronous behaviour so that the ConfWizard class can be used synchronously.

I tried using a ManualResetEvent object (calling Set in the event handler) but calls to WaitOne are blocking and thus the event isn't processed hanging the application.

Any ideas on how to achieve this in .NET1.1?

UPDATE - Aug 21, 2009
I had some time to experiment today and here's what I found.

WaitOne - if given a large enough timeout will work every time simply by stalling the application. Unfortunately that timeout needs to be at least 5 seconds (longer than I care to wait). Without a timeout, it still hangs. The event that calls set simply never happens.

Sleeping - same as WaitOne in that with a long enough timeout it will seem to work.

Threading - I don't want the UI to continue until the initialization is done because the behaviour of the UI is altered by the results of the initialization. However, splitting the initialization of the HelperClass object into a separate thread and calling Thread.Join to pause the main thread works.

So the solution to the problem seems to be using multiple threads in the right fashion.

You hack it and add a read only property on the config wizard that is set to true whenever the helper_ReadyEvent delegate is called. Then you could poll the property and show the dialog once the form is ready.

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

Or, couldn't you initialize the helper class prior to initializing the ConfigWizard?? Then you could just provide an reference to the helper class that's been initialized to the config form thru the classes constructor? Given the number of responses here it seems to me there are many ways you could accomplish the task.

I don't understand how the ManualResetEvent fails to work in your case. If you create one after you create your HelperClass instance and Set it in your ReadyEvent , then all you have to do is to add a WaitOne at the bottom of your ConfWizard constructor. Yes, the WaitOne will block but that is the behaviour (that your ConfWizard constructor does not return until everything is ready) you want isn't it?

My first thought was, "Use a wait handle", but as you said at the end of your post, that won't work as the event will try to raise on the UI thread, but its blocked as it's waiting on the UI thread.

(I assume this is why it doesn't work. If it raises on a background thread - just use a ManualResetEvent to signal the UI thread it's ready.)

One alternate solution is let the form show, even when the helper class isn't ready, and redirect all actions on the helper class to a queue of actions that get processed when it is ready:

private Queue actions = new Queue();

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

Then, when it's ready, you go through and process all the actions:

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

I tried using a ManualResetEvent object (calling Set in the event handler) but calls to WaitOne are blocking and thus the event isn't processed hanging the application.

That must mean that either ReadyEvent is being called on the same thread that the ctor is on, or that HelperClass needs the UI thread. That's somewhat of a pickle - as you can't really delay the constructor return then.

If HelperClass just needs to process some window message, then you can insert an Application.DoEvents call in there.

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

Otherwise - I think you'll either have to async the creation through a factory, or just deal with the fact that HelperClass may not be ready and reprocess or disable UI as appropriate.

Edit:

Is [DoEvents] just as scary as it was in VB6?

For many people , yes . But, IMO, it (as usual) depends on the scenario. You do have to be careful with it because of possible reentrancy - if you're running as the result of a window message, then you'll possibly need to guard against that.

FWIW, the most common use of DoEvents is to repaint the screen because of a long running op that'd otherwise hang the UI. In that case - yes, .NET gives you a lot better ways of handling it with threading. But, if you just want to yield the UI thread (which you do), I ( and others ) see no problem with some sparingly and carefully placed DoEvent calls. And, frankly, I think it's the least complicated of your options (short of rewriting HelperClass, of course).

why don't you hook up the

helper.StartUp();

to the helper.ReadyEvent as well?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM