How to Invoke a UI Method from Another Thread

How to invoke a UI method from another thread

I guess your code is just a test so I won't discuss about what you do with your timer. The problem here is how to do something with an user interface control inside your timer callback.

Most of Control's methods and properties can be accessed only from the UI thread (in reality they can be accessed only from the thread where you created them but this is another story). This is because each thread has to have its own message loop (GetMessage() filters out messages by thread) then to do something with a Control you have to dispatch a message from your thread to the main thread. In .NET it is easy because every Control inherits a couple of methods for this purpose: Invoke/BeginInvoke/EndInvoke. To know if executing thread must call those methods you have the property InvokeRequired. Just change your code with this to make it works:

if (elapsedTime < MaxTime)
{
this.BeginInvoke(new MethodInvoker(delegate
{
this.lblElapsedTime.Text = elapsedTime.ToString();

if (ElapsedCounter % 2 == 0)
this.lblValue.Text = "hello world";
else
this.lblValue.Text = "hello";
}));
}

Please check MSDN for the list of methods you can call from any thread, just as reference you can always call Invalidate, BeginInvoke, EndInvoke, Invoke methods and to read InvokeRequired property. In general this is a common usage pattern (assuming this is an object derived from Control):

void DoStuff() {
// Has been called from a "wrong" thread?
if (InvokeRequired) {
// Dispatch to correct thread, use BeginInvoke if you don't need
// caller thread until operation completes
Invoke(new MethodInvoker(DoStuff));
} else {
// Do things
}
}

Note that current thread will block until UI thread completed method execution. This may be an issue if thread's timing is important (do not forget that UI thread may be busy or hung for a little). If you don't need method's return value you may simply replace Invoke with BeginInvoke, for WinForms you don't even need subsequent call to EndInvoke:

void DoStuff() {
if (InvokeRequired) {
BeginInvoke(new MethodInvoker(DoStuff));
} else {
// Do things
}
}

If you need return value then you have to deal with usual IAsyncResult interface.

How it works?

A GUI Windows application is based on the window procedure with its message loops. If you write an application in plain C you have something like this:

MSG message;
while (GetMessage(&message, NULL, 0, 0))
{
TranslateMessage(&message);
DispatchMessage(&message);
}

With these few lines of code your application wait for a message and then delivers the message to the window procedure. The window procedure is a big switch/case statement where you check the messages (WM_) you know and you process them somehow (you paint the window for WM_PAINT, you quit your application for WM_QUIT and so on).

Now imagine you have a working thread, how can you call your main thread? Simplest way is using this underlying structure to do the trick. I oversimplify the task but these are the steps:

  • Create a (thread-safe) queue of functions to invoke (some examples here on SO).
  • Post a custom message to the window procedure. If you make this queue a priority queue then you can even decide priority for these calls (for example a progress notification from a working thread may have a lower priority than an alarm notification).
  • In the window procedure (inside your switch/case statement) you understand that message then you can peek the function to call from the queue and to invoke it.

Both WPF and WinForms use this method to deliver (dispatch) a message from a thread to the UI thread. Take a look to this article on MSDN for more details about multiple threads and user interface, WinForms hides a lot of these details and you do not have to take care of them but you may take a look to understand how it works under the hood.

Run method on UI thread from another thread

The SynchronizationContext class was meant to solve this problem. Copy the value of its Current property in the constructor, use its Post() or Send() method later. This ensures your library will work with any GUI class library. Like this:

class MediaPlayer {
public MediaPlayer() {
callersCtx = System.Threading.SynchronizationContext.Current;
//...
}

private void FireOnTrackComplete() {
if (callersCtx == null) FireOnTrackCompleteImpl();
else callersCtx.Post(new System.Threading.SendOrPostCallback((_) => FireOnTrackCompleteImpl()), null);
}

protected virtual void FireOnTrackCompleteImpl() {
var handler = OnTrackComplete;
if (handler != null) handler(this, loadedTrack);
}

private System.Threading.SynchronizationContext callersCtx;
}

Passing UI Thread method to another thread for calling in C#

You can capture current SynchronizationContext in constructor and then Post your event handler invocation to it, like this:

public class WaveOutPlayer {
private readonly SynchronizationContext _context;
public WaveOutPlayer() {
// capture
_context = SynchronizationContext.Current;
}

public event EventHandler<AudioEventArgs> BufferSwapped;

private void passAudio(byte[] pAudioData) {
var args = new AudioEventArgs();
args.Data = pAudioData;
var handler = BufferSwapped;
if (handler != null) {
if (_context != null)
// post
_context.Post(_ => handler(this, args), null);
else
handler(this, args);
}
}
}

By doing this you do not introduce dependency on winforms to your WaveOutPlayer while at the same time no compilcated actions are required from WinForms part, event handlers are just invoked on UI thread. Note that Post here will be analog to Control.BeginInvoke. If you want analog of Control.Invoke - use Send instead.

Invoking function on another thread in .Net Core

As supposed by Panagiotis Kanavos

public class AudioFileSearcher
{
// task caller
public AudioFileSearcher(string searchPath, bool includeSubFolders, Views.SoundListView.SoundList parentView)
{
this.progress1 = new Progress<int>(
{
backgroundWorker1_ProgressChanged();
});
Task.Run(async () => await FindAudioFiles(progress1));
}
// reference to be updated by task and read by event Handler
private Progress<int> progress1 { get; set; }
// do something on task progress
void backgroundWorker1_ProgressChanged()
{
// Update UI on main thread or whatever
}
// Task
public async Task FindAudioFiles(IProgress<int> progress)
{
foreach(AudioItem itemToProcess in allFoundaudioItems)
{
// do long processing in background task
// a new element has been generated, update UI
if (progress != null) progress.Report(0); // ~fire event
}
}
}

works like a charm, items are beeing loaded
Sample Image

Call a Form method from a different thread (Invoke)

In case anyone else has issues with this in the future and, like me, doesn't find many c++ code examples, I'll post my solution.

In my GUI.h file, I've got a method to SetConsoleTextBoxText(). This is usable only by the thread which owns consoleTextBox. Therefore, any other thread which trys to call that method will need to Invoke() the method (which is to relinquish control back to the owning thread).

//GUI.h
delegate void MyDelegate(System::String ^ text);

void SetConsoleTextBoxText(System::String^ input)
{
if (this->consoleTextBox->InvokeRequired) //is a thread other than the owner trying to access?
{
MyDelegate^ myD = gcnew MyDelegate(this, &GUI::SetConsoleTextBoxText);
//GUI is the ref class. Replace with wherever your function is located.
this->Invoke(myD, gcnew array<Object^> { input }); //Invoke the method recursively
}
else
{
//Normal function of this method. This will be hit after a recursive call or from the owning thread
this->consoleTextBox->Text = input;
this->consoleTextBox->Refresh();
}
}

How do I update the GUI from another thread?

For .NET 2.0, here's a nice bit of code I wrote that does exactly what you want, and works for any property on a Control:

private delegate void SetControlPropertyThreadSafeDelegate(
Control control,
string propertyName,
object propertyValue);

public static void SetControlPropertyThreadSafe(
Control control,
string propertyName,
object propertyValue)
{
if (control.InvokeRequired)
{
control.Invoke(new SetControlPropertyThreadSafeDelegate
(SetControlPropertyThreadSafe),
new object[] { control, propertyName, propertyValue });
}
else
{
control.GetType().InvokeMember(
propertyName,
BindingFlags.SetProperty,
null,
control,
new object[] { propertyValue });
}
}

Call it like this:

// thread-safe equivalent of
// myLabel.Text = status;
SetControlPropertyThreadSafe(myLabel, "Text", status);

If you're using .NET 3.0 or above, you could rewrite the above method as an extension method of the Control class, which would then simplify the call to:

myLabel.SetPropertyThreadSafe("Text", status);

UPDATE 05/10/2010:

For .NET 3.0 you should use this code:

private delegate void SetPropertyThreadSafeDelegate<TResult>(
Control @this,
Expression<Func<TResult>> property,
TResult value);

public static void SetPropertyThreadSafe<TResult>(
this Control @this,
Expression<Func<TResult>> property,
TResult value)
{
var propertyInfo = (property.Body as MemberExpression).Member
as PropertyInfo;

if (propertyInfo == null ||
!@this.GetType().IsSubclassOf(propertyInfo.ReflectedType) ||
@this.GetType().GetProperty(
propertyInfo.Name,
propertyInfo.PropertyType) == null)
{
throw new ArgumentException("The lambda expression 'property' must reference a valid property on this Control.");
}

if (@this.InvokeRequired)
{
@this.Invoke(new SetPropertyThreadSafeDelegate<TResult>
(SetPropertyThreadSafe),
new object[] { @this, property, value });
}
else
{
@this.GetType().InvokeMember(
propertyInfo.Name,
BindingFlags.SetProperty,
null,
@this,
new object[] { value });
}
}

which uses LINQ and lambda expressions to allow much cleaner, simpler and safer syntax:

// status has to be of type string or this will fail to compile
myLabel.SetPropertyThreadSafe(() => myLabel.Text, status);

Not only is the property name now checked at compile time, the property's type is as well, so it's impossible to (for example) assign a string value to a boolean property, and hence cause a runtime exception.

Unfortunately this doesn't stop anyone from doing stupid things such as passing in another Control's property and value, so the following will happily compile:

myLabel.SetPropertyThreadSafe(() => aForm.ShowIcon, false);

Hence I added the runtime checks to ensure that the passed-in property does actually belong to the Control that the method's being called on. Not perfect, but still a lot better than the .NET 2.0 version.

If anyone has any further suggestions on how to improve this code for compile-time safety, please comment!

Accessing UI from outside thread in Winforms

You MUST access your UI from your UI Thread only. But you can use the Control.Invoke method - a Form IS a Control - to ensure your code is run from the UI Thread.

Also, you have a static BindingList, but I assume you want to display the contents of that list in an specific form. You should make the BindingList an instance member instead... or get a reference to a valid Form. The Control.Invoke method is not static.

There are several ways to do so. I would do it like so:

First, create a method in your Form class that adds the record to the list.

public void AddRecord(Record r) {
if(this.InvokeRequired) {
this.Invoke(new MethodInvoker(() => this.AddRecord(r)));
} else {
this.record_list.Add(r);
}
}

Second, you need to have a reference to the form (in the next step, that is the theForm variable).

Then, in your listener method, invoke AddRecord method instead of adding the record in your BindingList directly.

public static void listen(IPEndPoint server_ip)
{
Console.WriteLine("In listen");
while (true)
{
try
{
byte[] received_bytes = udp_client.Receive(ref server_ip);
string received_data = Encoding.ASCII.GetString(received_bytes);
Record record = JsonConvert.DeserializeObject<Record>(received_data);
theForm.AddRecord(record); // You need a Form instance.
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
}

How to call a control method from another thread

This answer is exclusively focused on showing how to use properly (i.e., by maximising its in-built functionalities) BackgroundWorker (it is the continuation of some of the comments I wrote in a previous post of the OP) to deliver the intended functionalities.

To use the code below these lines, start a new Winforms project and add the following controls to the main form: Button (button1 with the click event button1), RichTextBox (richTextBox1) and a BackgroundWorker (backgroundWorker1 with the DoWork event backgroundWorker1_DoWork and the ProgressChanged event backgroundWorker1_ProgressChanged); also note that Form1_Load is the Load event of the main form.

To use the application, just input any text in the richTextBox1 by including some of the hardcoded words (i.e., "word1", "word2", "word3", "word4", "word5"), click on button1 and confirm that they are highlighted as expected.

volatile int curWordStartIndex; //I use this global variable to communication between the progressChanged event and findBit, called from the DoWork event

private void Form1_Load(object sender, EventArgs e)
{
backgroundWorker1.WorkerReportsProgress = true;
}

private void button1_Click(object sender, EventArgs e)
{
//As far as richTextBox1.TextLength provokes a cross-thread error, I pass it as an argument
backgroundWorker1.RunWorkerAsync(richTextBox1.TextLength);
}

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
findBit((int)e.Argument);
}

private void findBit(int textLength)
{
string[] words = new string[] { "word1", "word2", "word3", "word4", "word5" };
foreach (string word in words)
{
int startIndex = 0;
while (startIndex < textLength)
{
//Rather than performing the actions affecting the GUI thread here, I pass all the variables I need to
//the ProgressChanged event through ReportProgress and perform the modifications there.
backgroundWorker1.ReportProgress(0, new object[] { word, startIndex, Color.Yellow });
if (curWordStartIndex == -1) break;

startIndex += curWordStartIndex + word.Length;
}
}
}

private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
object[] curVars = (object[])e.UserState;

richTextBox1.SuspendLayout();

string word = (string)curVars[0];
int startIndex = (int)curVars[1];
Color curColor = (Color)curVars[2];
curWordStartIndex = richTextBox1.Find(word, startIndex, RichTextBoxFinds.None);

if (curWordStartIndex != -1)
{
richTextBox1.SelectionStart = curWordStartIndex;
richTextBox1.SelectionLength = word.Length;
richTextBox1.SelectionBackColor = curColor;
}

richTextBox1.ResumeLayout();
}


Related Topics



Leave a reply



Submit