Wrapping an asynchronous method synchronously in C#
Asked Answered
I

4

8

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

Ibeam answered 24/1, 2009 at 6:20 Comment(2)
I'm not clear. Does your code run properly when it runs asynchronously in a non windows application? Or does it always block indefinitely in a non windows application?Ichthyo
In a non windows application, it blocks indefinitely because the _Complete event never fires, despite trying ManualResetEvents etc. In a windows form application, it works fine.Ibeam
I
8

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();
    }
}
Ingleside answered 24/1, 2009 at 6:46 Comment(4)
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 :)Schade
I suspect that may be the case, but it's still worth pointing out the technique. You never know. :)Ingleside
Sure.. one less person sitting in a while loop hammering the CPU for no reason is a good thing :-DSchade
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.Ibeam
R
1

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.

Rockey answered 28/1, 2009 at 2:47 Comment(1)
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.Dustup
P
1
AutoResetEvent _autoEvent = new AutoResetEvent(false);

public  WebBrowser SyncronNavigation(string url)
{
    WebBrowser wb  = null;

    wb = new WebBrowser();
    wb.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(wb_DocumentCompleted);

    wb.ScriptErrorsSuppressed = true;
    wb.Navigate(new Uri(url));

    while (!_autoEvent.WaitOne(100))
        Application.DoEvents();

    return wb;
}

void wb_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
    //throw new NotImplementedException();
    _autoEvent.Set();
}
Profant answered 1/10, 2012 at 14:11 Comment(0)
S
0

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.

Schade answered 24/1, 2009 at 6:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.