views:

1425

answers:

5

I have a third party library containing a class which performs a function asynchronously. The class inherits from the Form. The function basically performs a calculation based on data stored in a database. Once it has finished, it calls a _Complete event in the calling form.

What I would like to do is call the function synchronously but from a non-windows form application. The problem is, no matter what I do, my application blocks and the _Complete event handler never fires. From a windows form I can simulate the function running synchronously by using a "complete" flag and a "while (!complete) application.doevents", but obviously application.doevents isnt available in a non-windows form application.

Is there something that would stop me using the class's method outside of a windows form application (due to it inheriting from 'Form') ? Is there some way I can work around this ?

Thanks, Mike

A: 

Do you have the source for the component? It sounds like it's relying on the fact it will be called from a WinForms environment (must be a good reason why a library inherits from Form!), but it's hard to know for sure.

Steven Robbins
A: 

No. Unfortunately I dont have the source.

I believe the component was written to be run from a form. Perhaps here in is the problem and a component that inherits from Form cannot be called from a non-windows form application. I havent received any compiler errors which indicate this is the case, but the fact that it always blocks when I call it, makes me wonder whether it is the problem.

mikecamimo
You won't necessarily get compiler errors if you are referencing the right assemblies, but it doesn't mean all the things that this code is assuming are going to be there are going to be instantiated. Sounds like a horrible component if that's the case though ;)
Steven Robbins
+2  A: 

At a stab it might be worth trying something like the following which uses a WaitHandle to block the current thread rather than spinning and checking a flag.

using System;
using System.Threading;

class Program
{
    AutoResetEvent _autoEvent;

    static void Main()
    {
     Program p = new Program();
     p.RunWidget();
    }

    public Program()
    {
     _autoEvent = new AutoResetEvent(false);
    }

    public void RunWidget()
    {
     ThirdParty widget = new ThirdParty();   
     widget.Completed += new EventHandler(this.Widget_Completed);
     widget.DoWork();

     // Waits for signal that work is done
     _autoEvent.WaitOne();
    }

    // Assumes that some kind of args are passed by the event
    public void Widget_Completed(object sender, EventArgs e)
    {
     _autoEvent.Set();
    }
}
Kev
I think he only did that inside a WinForms test harness (which worked), I think the problem is that the operation never completes when it's not in a Winforms app, so it won't really matter how you wait for it if it's never going to finish :)
Steven Robbins
I suspect that may be the case, but it's still worth pointing out the technique. You never know. :)
Kev
Sure.. one less person sitting in a while loop hammering the CPU for no reason is a good thing :-D
Steven Robbins
I added [STATThread] and it now runs without error, but locks up on the WaitOne .. which is basically what I have found previously. The _Complete event never seems to fire.
mikecamimo
A: 

Thanks Kev.

I wrapped your code around the thirdparty object. I firstly had to add a reference to System.Windows.Forms.

When I run it, I get the message "activex control 'xxx' cannot be instantiated because the crrent thread is not in a single threaded apartment". This occurs on the "ThirdParty widget = new ThirdParty();" line.

mikecamimo
Might be worth sticking [STAThread] on the line before the class declaration. Incidentally, both of your replies should be COMMENTS to the other replies, not separate replies themselves.. click the comments links.
Steven Robbins
My apologies steve .. first time poster .. as if you couldnt tell :)
mikecamimo
You don't have to apologise.. just pointing it out for future reference :)
Steven Robbins
A: 

I've got some more information on this problem (I'm working in the same team as mikecamimo).

The problem also occurs in the Windows Forms application, when replicated correctly. In the original OP, the problem didn't occur in the windows form because there was no blocking. When blocking is introduced by using a ResetEvent, the same problem occurs.

This is because the event handler (Widget_Completed) is on the same thread as the method calling Widget.DoWork. The result that AutoResetEvent.WaitOne(); blocks forever because the event handler is never called to Set the event.

In a windows forms environment this can worked around by using Application.DoEvents to poll the message queue and allow the event the be handled. See below.

using System;
using System.Threading;
using System.Windows.Forms;

class Program
{
    EventArgs data;

    static void Main()
    {
        Program p = new Program();
        p.RunWidget();
    }

    public Program()
    {
        _autoEvent = new AutoResetEvent(false);
    }

    public void RunWidget()
    {
        ThirdParty widget = new ThirdParty();                   
        widget.Completed += new EventHandler(this.Widget_Completed);
        data = null;
        widget.DoWork();

        while (data == null);
            Application.DoEvents();

        // do stuff with the results of DoWork that are contained in EventArgs.
    }

    // Assumes that some kind of args are passed by the event
    public void Widget_Completed(object sender, EventArgs e)
    {
        data = e;
    }
}

In a non windows forms application, such as a Windows Service, Application is not available so DoEvents cannot be called.

The problem is one of threading and that widget.DoWork's associated event handler somehow needs to be on another thread. This should prevent AutoResetEvent.WaitOne from blocking indefinitely. I think... :)

Any ideas on how to accomplish this would be fantastic.

rob_g
I know this a long time ago, but you could try running the third party code in a different thread, i.e. explicitly starting a new thread to run it in. This way WaitOne will block on a different thread, and the Completed event will (should) fire.
Schmuli