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. Just don’t call BeginInvoke/Invoke in the form constructor.

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:

WinForm - Several threads writing to a textbox (timestamp, thread id, and message)

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

Here is the code showing how to update the UI from different threads with BeginInvoke:

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

Code language: C# (cs)

5 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.

    • 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.

    • Hi Rakesh,

      I’m glad it helped you. Also, thanks for sharing that interesting link about the Progress class.

Comments are closed.