views:

354

answers:

4

I'm building a non-visual component in .Net 2.0. This component uses an asynchronous socket (BeginReceive, EndReceive etc). Asynchronous callbacks are called in the context of a worker thread created by the runtime. The component user shouldn't have to worry about multithreading (This is the main goal, what I want)

The component user can create my non-visual component in any thread (the UI thread is just a common thread for simple applications. More serious applications could create the component within an arbitrary worker thread). The component trigger events such as "SessionConnected" or "DataAvailable".

The issue: because of the Async Callbacks and the events raised therein the event handler is executed in the worker thread context. I want to use an intermediate layer which force the event handler to execute in the context of the thread which created the component at the first place.

Example code (stripped from exception handling etc...)

    /// <summary>
    /// Occurs when the connection is ended
    /// </summary>
    /// <param name="ar">The IAsyncResult to read the information from</param>
    private void EndConnect(IAsyncResult ar)
    {
        // pass connection status with event
        this.Socket.EndConnect(ar);

        this.Stream = new NetworkStream(this.Socket);

        // -- FIRE CONNECTED EVENT HERE --

        // Setup Receive Callback
        this.Receive();
    }


    /// <summary>
    /// Occurs when data receive is done; when 0 bytes were received we can assume the connection was closed so we should disconnect
    /// </summary>
    /// <param name="ar">The IAsyncResult that was used by BeginRead</param>
    private void EndReceive(IAsyncResult ar)
    {
        int nBytes;
        nBytes = this.Stream.EndRead(ar);
        if (nBytes > 0)
        {
            // -- FIRE RECEIVED DATA EVENT HERE --

            // Setup next Receive Callback
            if (this.Connected)
                this.Receive();
        }
        else
        {
            this.Disconnect();
        }
    }

Because of the nature of the Async sockets all applications using my component are littered with "If (this.InvokeRequired) { ..." and all I want is the user to be able to use my component worry-free as sort of a drop-in.

So how would I go about raising the events without requiring the user to check InvokeRequired (or, put differently, how do I force the events raised in the same thread as the thread that initiated the event in the first place)?

I have read stuff about AsyncOperation, BackgroundWorkers, SynchronizingObjects, AsyncCallbacks and tons of other stuff but it all makes my head spin.

I did come up with this, surely clumsy, "solution" but it seems to fail in some situations (when my component is called from a WinForms project via a static class for example)

    /// <summary>
    /// Raises an event, ensuring BeginInvoke is called for controls that require invoke
    /// </summary>
    /// <param name="eventDelegate"></param>
    /// <param name="args"></param>
    /// <remarks>http://www.eggheadcafe.com/articles/20060727.asp&lt;/remarks&gt;
    protected void RaiseEvent(Delegate eventDelegate, object[] args)
    {
        if (eventDelegate != null)
        {
            try
            {
                Control ed = eventDelegate.Target as Control;
                if ((ed != null) && (ed.InvokeRequired))
                    ed.Invoke(eventDelegate, args);
                else
                    eventDelegate.DynamicInvoke(args);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.GetType());
                Console.WriteLine(ex.Message);
                //Swallow
            }
        }
    }

Any help would be appreciated. Thanks in advance!

EDIT: According to this thread my best bet would be to use SyncrhonizationContext.Post but I can't see how to apply it to my situation.

A: 

Maybe I'm not understanding the issue, but it seems to me that you can just pass a reference to a custom object in your Async state.

I put together the following example to illustrate;

First we have a Callback object. This has 2 properties -- a Control on which to dispatch actions and an Action to call;

public class Callback
{
    public Control Control { get; set; }
    public Action Method { get; set; }
}

Then I have a WinForms project that calls some random code on another thread (using BeginInvoke) and then shows a messagebox when the code finishes executing.

    private void Form1_Load(object sender, EventArgs e)
    {
        Action<bool> act = (bool myBool) =>
            {
                Thread.Sleep(5000);
            };

        act.BeginInvoke(true, new AsyncCallback((IAsyncResult result) =>
        {
            Callback c = result.AsyncState as Callback;
            c.Control.Invoke(c.Method);

        }), new Callback()
        {
            Control = this,
            Method = () => { ShowMessageBox(); }
        });            
    }

The ShowMessageBox method must run on the UI thread and looks like:

    private void ShowMessageBox()
    {
        MessageBox.Show("Testing");
    }

Is this what you were looking for?

statichippo
Not really; this is way to complex for people when using my component. All the do is reference the component and use code like: //Initialize MyComponent with IP, port etc. and then: MyComponent.SendString("This is cool");The MyComponent raises events; whether they are handled by a GUI or non-GUI project should not matter and I don't want the user to be responsible to check for InvokeRequired.
ComponentBuilder
A: 

If your component must always be used by the same thread, you could do something like this:

public delegate void CallbackInvoker(Delegate method, params object[] args);

public YourComponent(CallbackInvoker invoker)
{
    m_invoker = invoker;
}

protected void RaiseEvent(Delegate eventDelegate, object[] args)
{
    if (eventDelegate != null)
    {
        try
        {
            if (m_invoker != null)
                m_invoker(eventDelegate, args);
            else
                eventDelegate.DynamicInvoke(args);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.GetType());
            Console.WriteLine(ex.Message);
            //Swallow
        }
    }
}

Then when you instantiate your component from a Form or other control you can do this:

YourComponent c = new YourComponent(this.Invoke);

To queue the event on a non UI worker thread, it must have some sort of work queuing mechanism, then you can give a method with CallbackInvoker's signature to queue the delegate on the worker thread.

SelflessCoder
This puts too much complexity in the lap of the user using my component.
ComponentBuilder
Where is that complexity? Specifying a delegate in the component's constructor?
SelflessCoder
I want my component to be transparently Asynchronous (so the enduser doesn't notice). When the enduser has to pass a delegate for no (obvious) reason I think it's not 'userfriendly'.I seem to have found my solution though; but I'm not sure if it's "the best" solution.
ComponentBuilder
A: 

I seem to have found my solution:

    private SynchronizationContext _currentcontext

    /// Constructor of my component:
    MyComponent() {
        _currentcontext = WindowsFormsSynchronizationContext.Current;
       //...or...?
        _currentcontext = SynchronizationContext.Current;
    }

    /// <summary>
    /// Raises an event, ensuring the correct context
    /// </summary>
    /// <param name="eventDelegate"></param>
    /// <param name="args"></param>
    protected void RaiseEvent(Delegate eventDelegate, object[] args)
    {
        if (eventDelegate != null)
        {
            if (_currentcontext != null)
                _currentcontext.Post(new System.Threading.SendOrPostCallback(
                    delegate(object a)
                    {
                        eventDelegate.DynamicInvoke(a as object[]);
                    }), args);
            else
                eventDelegate.DynamicInvoke(args);
        }
    }

I'm still testing this but it seems to work fine.

ComponentBuilder
What will happen with this approach when your component is not created on the UI thread?
SelflessCoder
I forgot to paste my latest edit, which checks if _currentcontext is null; this is now fixed (and edited).
ComponentBuilder
I just checked, if you capture the SynchronizationContext in the component's constructor and the component is created in a non UI thread, your eventDelegate will be executed on the ThreadPool.
SelflessCoder
I use WindowsFormsSynchronizationContext now (I editted my "solution" again). Would that make any difference?I am pretty clueless on this topic I'm afraid.I checked this with a Console application and with a WinForms application and both seem to work OK.
ComponentBuilder
+1  A: 

Ok; so here's what I ended up with after some more reading:

public class MyComponent {
    private AsyncOperation _asyncOperation;

    /// Constructor of my component:
    MyComponent() {
        _asyncOperation = AsyncOperationManager.CreateOperation(null);
    }

    /// <summary>
    /// Raises an event, ensuring the correct context
    /// </summary>
    /// <param name="eventDelegate"></param>
    /// <param name="args"></param>
    protected void RaiseEvent(Delegate eventDelegate, object[] args)
    {
        if (eventDelegate != null)
        {
            _asyncOperation.Post(new System.Threading.SendOrPostCallback(
                delegate(object argobj)
                {
                    eventDelegate.DynamicInvoke(argobj as object[]);
                }), args);
        }
    }
}

The other solution posted here was sort of a work-in-progress. The solution posted here seems (according to MSDN) be the best so far. Suggestions are very, very welcome.

ComponentBuilder