Fire event from Async component in UI thread

I am creating 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 the workflow created by the runtime. The component user does not need to worry about multithreading (this is the main goal that I want)

A component user can create my non-visual component in any thread (the user interface thread is just a common thread for simple applications. More serious applications can create a component in an arbitrary workflow). The component fires events such as "SessionConnected" or "DataAvailable".

Problem: Due to asynchronous callbacks and related events, the event handler runs in the context of the workflow. I want to use an intermediate layer that enhances the event handler to execute in the context of the thread that created the component in the first place.

Sample code (devoid of 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(); } } 

Due to the nature of Async sockets, all applications using my component are dotted with "If (this.InvokeRequired) {...", and all I want is that the user can use my component without problems like a drop sort.

So, how could I raise events without requiring the user to check InvokeRequired (or, in other words, how can I force events raised in the same thread as the thread that raised the event in the first place)?

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

I really came up with this, of course, a clumsy β€œsolution”, but it doesn't seem to work in some situations (when my component is called from a WinForms project through 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</remarks> 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 greatly appreciated. Thanks in advance!

EDIT: According to this topic , the best option would be to use SyncrhonizationContext.Post, but I don't see how to apply it to my situation.

+7
user-interface c # asynchronous sockets invokerequired
source share
4 answers

OK; so here is 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 kind of incomplete. The solution posted here seems (by MSDN) to be the best so far. Suggestions are very, very welcome.

+2
source share

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 be working fine.

+1
source share

I may not understand the problem, but it seems to me that you can just pass the link to the user object in your Async state.

I will give the following example to illustrate:

First we have a callback object. This object has 2 properties - a control to send actions to 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 in another thread (using BeginInvoke) and then shows a message box 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 should run in the user interface thread and looks like this:

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

Is this what you were looking for?

0
source share

If your component should always be used by the same thread, you can 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 a component from a form or other control, you can do this:

 YourComponent c = new YourComponent(this.Invoke); 

To put an event in a non-interface workflow, it must have some kind of mechanism for working with the queue, then you can give a method with the CallbackInvoker signature to put the delegate queue in the workflow.

0
source share

All Articles