How to update UI from another thread in C#
By FoxLearn 12/21/2024 4:11:43 AM 113
If you try to modify UI controls from another thread, you'll encounter exceptions, such as InvalidOperationException in Windows Forms.
System.InvalidOperationException: 'Cross-thread operation not valid: Control accessed from a thread other than the thread it was created on.'
In this article, we will explore how to execute multiple threads concurrently, update the UI based on their results, and avoid the "cross-thread operation" error by using BeginInvoke
(or Invoke
), which ensures that UI updates are performed on the UI thread.
Update the UI from Another Thread in C# Windows Forms Application
In Windows Forms (WinForms) applications, UI controls, such as TextBox
, Label
, or DataGridView
, are created on the UI thread and can only be updated from that same thread. If you try to update the UI from a different thread, you'll encounter the exception shown above.
This can be done using the Control.BeginInvoke
or Control.Invoke
methods.
Invoke
: This method is synchronous, meaning the calling thread will wait for the UI thread to finish updating before continuing.BeginInvoke
: This method is asynchronous, meaning the calling thread can continue executing without waiting for the UI update.
We simulate running several tasks concurrently. Each task represents a simulated operation (e.g., a delayed HTTP GET request or a computational task). The results of these tasks will be logged in a TextBox
as each task completes.
The form contains a TextBox
(txtLog
) to display log messages, a Button
(btnStartThreads
) to start the tasks, and a NumericUpDown
(numThreads
) to allow the user to specify how many threads to run.
using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace ThreadingExample { public partial class frmUpdateUIThread : Form { Control control; public frmUpdateUIThread() { InitializeComponent(); control = txtLog; // This can be any control, like a TextBox } // Method to log messages to the UI thread private void Log(string msg) { string m = $"{DateTime.Now.ToString("H:mm:ss.fffff")}\t{msg}\n"; control.BeginInvoke((MethodInvoker)delegate () { txtLog.AppendText(m); // Append log message to the TextBox txtLog.ScrollToCaret(); // Scroll to the latest log entry }); } // Start multiple threads and log their results in the UI 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 delay = TimeSpan.FromMilliseconds(random.Next(1000, 5000)); // Simulate random work time var task = Task.Run(async () => { var tId = $"Task ID {j}, ThreadID = {Thread.CurrentThread.ManagedThreadId}"; Log($"{tId} starting processing"); await Task.Delay(delay); // Simulate some background work Log($"{tId} finished. Took {delay.TotalSeconds} seconds"); }); allTasks.Add(task); } // Wait for all tasks to finish await Task.WhenAll(allTasks); // This ensures that the main thread waits for all background tasks to complete before logging the final message. // Final log entry after all tasks are complete Log("All tasks have finished"); } } }
In the constructor, we assign the TextBox
control (txtLog
) to the control
variable. The Log
method takes a message, appends a timestamp, and uses BeginInvoke
to safely update the TextBox
on the UI thread.
In the btnStartThreads_Click
method, we create several background tasks (Task.Run
). Each task runs a simple loop with a random delay to simulate background work (e.g., a network request or computational task). The task logs a message to the UI thread when it starts and when it finishes.
BeginInvoke
ensures that each log message is added to the TextBox
on the UI thread. This prevents the cross-thread operation error and ensures that the UI remains responsive.
Always remember that UI controls can only be updated on the UI thread. If you need to update the UI based on results from background threads, always use Invoke
or BeginInvoke
.
While BeginInvoke
is asynchronous, the method is still relatively efficient. However, you should avoid excessive UI updates (e.g., updating every millisecond) because frequent UI updates can cause the application to become unresponsive.
In applications where you need to run multiple tasks concurrently and update the UI based on their results, you must marshal the UI updates back to the UI thread. In WinForms, this can be done easily using Control.BeginInvoke
or Control.Invoke
.
- How to get CheckedListBox selected values in C#
- How to use Advanced Filter DataGridView in C#
- How to create a Notification Popup in C#
- How to Use Form Load and Button click Event in C#
- How to Link Chart /Graph with Database in C#
- How to Check SQL Server Connection in C#
- How to Generate Serial Key in C#
- How to Search DataGridView by using TextBox in C#