How to update UI from another thread

I often need to be able to run multiple threads and update the UI based on the results. For example, I may need to execute GET requests to 10 different endpoints concurrently, and then report their results in a datagrid as they come back.

The problem is you can’t just update the UI from any thread. It must be on the UI thread. Otherwise you get the following error:

System.InvalidOperationException: ‘Cross-thread operation not valid: Control accessed from a thread other than the thread it was created on.’

The solution is to call control.BeginInvoke, passing in a MethodInvoker delegate. The code in the delegate will be executed on the UI thread, hence allowing you to update the UI.

Note: BeginInvoke() is asynchronous. Invoke() is synchronous. You can use either.

Example app

I created a simple app that runs multiple threads at once, and logs the results in the UI as they finish. Here’s what it looks like:

Note: The threads aren’t doing anything useful, they’re just delaying for a random amount of time. This is to simulate real work being done, and simplifies the example.

Code

using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace ThreadingExample { public partial class frmUpdateUIFromThread : Form { Control control; public frmUpdateUIFromThread() { InitializeComponent(); control = txtLog; //this can be any control } private void Log(string msg) { string m = $"{DateTime.Now.ToString("H:mm:ss.fffff")}\t{msg}\n"; control.BeginInvoke((MethodInvoker)delegate () { txtLog.AppendText(m); txtLog.ScrollToCaret(); }); } private async void btnStartThreads_Click(object sender, EventArgs e) { Random random = new Random(); List<Task> allTasks = new List<Task>(); for (int i = 1; i <= (int)numThreads.Value; i++) { var j = i; var delayFor = TimeSpan.FromMilliseconds(random.Next(100, 5000)); var task = Task.Run(async () => { var idForLog = $"Task ID {j}, ThreadID={Thread.CurrentThread.ManagedThreadId}"; Log($"{idForLog} starting processing"); await Task.Delay(delayFor); Log($"{idForLog} finished. Took {delayFor.TotalSeconds}"); }); allTasks.Add(task); } await Task.WhenAll(allTasks); Log("All tasks have finished"); } } }

3 thoughts on “How to update UI from another thread”

  1. I take half of that remark back. The BeginInvoke bailed my ass out. But I have no clue why or how. It just works.

    Reply
    • I’m glad this helped.

      Let’s break this down.

      Control.BeginInvoke(delegate) accepts a delegate parameter and executes it on the UI thread.

      Think of a delegate as a reference to a method (like a function pointer).

      In the article I am passing in a MethodInvoker delegate. This has the following definition: public delegate void MethodInvoker();

      Notice that this has the same definition as Action:
      public delegate void Action();

      Action is the typical way of passing in void, parameterless delegates. I used MethodInvoker in this case instead, simply because I solved this problem years ago before Action was a thing in .NET.

      If you want to learn more about delegates, this article might help clear it up further: https://makolyte.com/csharp-pass-in-a-func-to-override-behavior/

      I hope that clears it up. Feel free to ask further questions if you have any.

      Reply

Leave a Comment