helrich
helrich

Reputation: 1310

Prevent necessity of InvokeRequired in event handler

I've been writing an API that facilitates communication with a serial port. I'm doing some refactoring and general cleanup and was wondering if there's a way to avoid the following issue.

The main class in the API has the capability to constantly read from the port and raise an event containing a value when the read bytes match a particular regex. The process of reading and parsing occurs on another thread. The event contains the value as an argument (string) and because it's being raised from another thread, a client attempting to directly assign the value to, say, the Text property of a control causes a cross-thread exception unless the handler has the proper Invoke code.

I understand why this happens, and when I put the proper invocation code in my test client's event handler, all is well; my question is whether or not there's anything I can do in the API code itself such that clients don't have to worry about it.

Essentially, I'd like to turn this:

void PortAdapter_ValueChanged(Command command, string value)
{
  if (this.InvokeRequired)
  {
    Invoke(new MethodInvoker(() =>
      {
        receivedTextBox.Text = value;
      }));
  }
  else
  {
    receivedTextBox.Text = value;
  }
}

into simply this:

void PortAdapter_ValueChanged(Command command, string value)
{
    receivedTextBox.Text = value;
}

Upvotes: 2

Views: 1123

Answers (3)

Gian Marco
Gian Marco

Reputation: 23169

You can trigger the event in the UI Thread, this way the event handler (if any) will already be in the UI thread.

public class PortAdapter
{
    public event EventHandler<string> ValueChanged;

    protected virtual void OnValueChanged(string e)
    {
        var handler = ValueChanged;
        if (handler != null)
        {
            RunInUiThread(() => handler(this, e));
        }
    }

    private void RunInUiThread(Action action)
    {
        if (InvokeRequired)
        {
            Invoke(action);
        }
        else
        {
            action.Invoke();
        }
    }
}

However this is not good design because you don't know if an handler will perform UI interaction.

Upvotes: 1

Sriram Sakthivel
Sriram Sakthivel

Reputation: 73442

Well there is a common pattern for that used many places in .Net framework itself. For example BackgroundWorker uses this model.

For that you'll take a SynchronizationContext as a parameter for your API, in this case I assume it is PortAdapter.

When raising an event, you raise the event in given SynchronizationContext using SynchronizationContext.Post or SynchronizationContext.Send. Former is asynchronous and latter is synchronous.

So, when client code creating a instance of your PortAdapter, it passes WindowsFormsSynchronizationContext instance as parameter. Which means that PortAdapter will raise the event in given synchronization context and that also means that you don't need a InvokeRequired or Invoke calls.

public class PortAdapter
{
    public event EventHandler SomethingHappened;

    private readonly SynchronizationContext context;
    public PortAdapter(SynchronizationContext context)
    {
        this.context = context ?? new SynchronizationContext();//If no context use thread pool
    }

    private void DoSomethingInteresting()
    {
        //Do something

        EventHandler handler = SomethingHappened;
        if (handler != null)
        {
            //Raise the event in client's context so that client doesn't needs Invoke
            context.Post(x => handler(this, EventArgs.Empty), null);
        }
    }
}

Client code:

PortAdapter adpater = new PortAdapter(SynchronizationContext.Current);
...

It is very important to create instance of PortAdapter in UI thread, otherwise SynchronizationContext.Current will be null and hence events will be still raised in ThreadPool thread.

More about SynchronizationContext here.

Upvotes: 3

Sinatr
Sinatr

Reputation: 21969

TBH, the approach with checking for InvokeRequired is fine and flexible.

But if you like, you can have all events in your application UI-safe. For this either all classes have to have invocation control registered

public class SomeClassWithEvent
{
    private static Control _invoke = null;

    public static void SetInvoke(Control control)
    {
        _invoke = control;
    }

    public event Action SomeEvent;
    public OnSomeEvent()
    {
        // this event will be invoked in UI thread
        if (_invoke != null && _invoke.IsHandleCreated && SomeEvent != null)
            _invoke.BeginInvoke(SomeEvent);
    }
}

// somewhere you have to register
SomeClassWithEvent.SetInvoke(mainWindow);

// and mayhaps unregister
SomeClassWithEvent.SetInvoke(null);

or have that invocation control exposed, to example:

// application class
public static class App
{
    // will be set by main window and will be used even risers to invoke event
    public static MainWindow {get; set;}
}

You will have difficulties if event occur when no handle is created or control registered.

Upvotes: 2

Related Questions