C# Multithreading — Invoke without a Control

荒凉一梦 提交于 2019-11-28 18:49:39

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.

Lasse Vågsæther Karlsen

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);

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.

You don't need the specific control, any control (including the Form) will do. So you could abstract it away from the UI somewhat.

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
    }
}

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.

I just ran into the same situation. However, in my case I couldn't use SynchronizationContext.Current, because I did not have access to any UI components and no callback to capture the current synchronization context. It turns out that if the code is not currently running in a Windows Forms messge pump, SynchronizationContext.Current will be set to a standard SynchronizationContext, which will just run Send calls on the current thread and Post calls on the ThreadPool.

I found this answer explaining the different types of synchronization contexts. In my case the solution was to create a new WindowsFormsSynchronizationContext object on the thread that would later start the message pump using Application.Run(). This synchronization context can then be used by other threads to run code on UI thread, without ever touching any UI components.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!