views:

579

answers:

5

I am only somewhat familiar with multi-threading in that I've read about it but have never used it in practice.

I have a project that uses a third party library that shares the status of an input device by raising events. The problem is, the way the library is written these events are raised from a different thread.

My application does not need to be multi-threaded and I've run into a lot of classic threading issues (UI controls complaining about being interacted with from a different thread, collections that get modified as one piece of code is iterating over it, etc.).

I just want the 3rd party library's event to be given back to my UI thread. Specifically what I think should happen is:

My class receives the event and the handler is being run on a different thread than the UI. I want to detect this condition (like with InvokeRequired), and then perform the equivalent of BeginInvoke to give control back to the UI thread. Then the proper notifications can be sent on up the class hierarchy and all of my data is only touched by the one thread.

The problem is, the class that is receiving these input events is not derived from Control and therefore doesn't have InvokeRequired or BeginInvoke. The reason for this is that I tried to cleanly separate UI and the underlying logic. The class is still being run on the UI thread, it just doesn't have any UI inside the class itself.

Right now I fixed the issue by ruining that separation. I pass in a reference to the control that will be displaying data from my class and using its Invoke methods. That seems like it defeats the whole purpose of separating them because now the underlying class has a direct dependence on my specific UI class.

Perhaps there's a way to save a reference to the thread that ran the constructor and then there's something in the Threading namespace that will perform the Invoke commands?

Is there a way around this? Is my approach completely wrong?

+2  A: 

The handling method could simply store the data into a member variable of the class. The only issue with cross-threading occurs when you want to update threads to controls not created in that thread context. So, your generic class could listen to the event, and then invoke the actual control you want to update with a delegate function.

Again, only the UI controls that you want to update need to be invoked to make them thread safe. A while ago I wrote a blog entry on a "Simple Solution to Illegal Cross-thread Calls in C#"

The post goes into more detail, but the crux of a very simple (but limited) approach is by using an anonymous delegate function on the UI control you want to update:

if (label1.InvokeRequired) {
  label1.Invoke(
    new ThreadStart(delegate {
      label1.Text = "some text changed from some thread";
    }));
} else {
  label1.Text = "some text changed from the form's thread";
}

I hope this helps. The InvokeRequired is technically optional, but Invoking controls is quite costly, so that check ensure it doesn't update label1.Text through the invoke if its not needed.

Ash
My problem is more along the lines of: Worker Thread from library notifies my app and I want to take immediate action. The issue is that that "immediate action" will be run on the worker thread, completely hosing up other things happening on the UI thread (things that aren't UI related -- enumerating lists of objects for example).
colithium
A: 

Is there a way around this?

Yes, the work-around would be for you to create a thread-safe queue.

  • Your event handler is invoked by the 3rd-party thread
  • Your event handler enqueues something (event data) onto a collection (e.g. a List) which you own
  • Your event handler does something to signal your own thead, that there's data in the collection for it to dequeue and process:
    • Your thread could be waiting on something (a mutex or whatever); when its mutex is signalled by the event handler, it wakes up and checks the queue.
    • Alternatively, instead of being signalled, it could wake up periodically (e.g. once per second or whatever) and poll the queue.

In either case, because your queue is being written by two different threads (the 3rd-party thread is enqueueing, and your thread is dequeueing), it needs to be a thread-safe, protected queue.

ChrisW
I saw a lot of examples along these lines. I might be wrong but wouldn't this technique not work when the dequeueing is the main UI thread that can't go to sleep and can't sit there polling the queue in a loop. I didn't mention this but the other thread raises events many many times a second.
colithium
You're right: this would only be appropiate if "your" thread which you wanted to invoke were *not* the UI thread.
ChrisW
+8  A: 

Look into the AsyncOperation class. You create an instance of AsyncOperation on the thread you want to call the handler on using the AsyncOperationManager.CreateOperation method. The argument I use for Create is usually null, but you can set it to anything. To call a method on that thread, use the AsyncOperation.Post method.

nasufara
Took me awhile to understand how the delegates are used but it works!
colithium
+6  A: 

Use SynchronizationContext.Current, which will point to something that you can synchronize with.

This will do the right thing™ depending on the type of application. For a WinForms application, it will run this on the main UI thread.

Specifically, use the SynchronizationContext.Send method, like this:

SynchronizationContext context =
    SynchronizationContext.Current ?? new SynchronizationContext();

context.Send(s =>
    {
        // your code here
    }, null);
Lasse V. Karlsen
+1. Note that `SynchronizationContext.Current` is null if the program is a console application. A good pattern is using `AsyncOperation` which captures the current `SynchronizationContext` on creation and *does the right thing++*™. http://msdn.microsoft.com/en-us/library/system.componentmodel.asyncoperation.aspx
dtb
Wasn't aware of that class, thanks for letting me know.
Lasse V. Karlsen
It is WinForms so it would work but my intention was to be independent of UI. Console app is pushing it in terms of UI I would consider but good to know it wont work.Still +1
colithium
+1  A: 

If you are using WPF:

You need a reference to the Dispatcher object which manages the UI thread. Then you can use the Invoke or BeginInvoke method on the dispatcher object to schedule an operation which takes place in the UI thread.

The simplest way to get the dispatcher is using Application.Current.Dispatcher. This is the dispatcher responsible for the main (and probably the only) UI thread.

Putting it all together:

class MyClass
{
    // Can be called on any thread
    public ReceiveLibraryEvent(RoutedEventArgs e)
    {
        if (Application.Current.CheckAccess())
        {
            this.ReceiveLibraryEventInternal(e);
        }
        else
        {
            Application.Current.Dispatcher.Invoke(
                new Action<RoutedEventArgs>(this.ReceiveLibraryEventInternal));
        }
    }

    // Must be called on the UI thread
    private ReceiveLibraryEventInternal(RoutedEventArgs e)
    {
         // Handle event
    }
}
antonm
This is about WPF, I don't see where the Question indicates that that is used.
Henk Holterman