Appending to RichTextBox in event handler, getting cross-thread operation not valid share-icon

bigteks

Member
Joined
Nov 7, 2019
Messages
7
The error is:

An unhandled exception of type 'System.InvalidOperationException' occurred in System.Windows.Forms.dll
Cross-thread operation not valid: Control 'richTextBox1' accessed from a thread other than the thread it was created on.

Here is the code:
C#:
public Form1()
        {
            InitializeComponent();
            string api_key = "#######";
            BlurbProvider provider = BlurbProvider.XYZ;
            RealTimeClient client = new RealTimeClient(provider, api_key: api_key);
            BlurbHandler handler = new BlurbHandler();

            client.RegisterBlurbHandler(handler);
            client.Join(new string[] { "ABC" });
            client.Connect();

            handler.OnBlurb += (IBlurb blurb) =>
            {
                // Console.WriteLine(blurb);

                richTextBox1.AppendText(Convert.ToString(blurb));

            };
        }
The richTextBox1.AppendText is where the error is generated.

What's the right way to handle that? I have played around with some BackgroundWorker/RunWorkerAsync examples I found on Google (I think the answer might be somewhere in that approach) but I am not sure how to connect this handler triggered code above to something that's allowed to update the richtextbox.

The example that I got the above code from was using the console to write results, I want to learn how to send those results to a richtextbox.

Using .NET 4.7.2
 
If you get that error message then it means that your event handler is being executed on a secondary thread. Any changes made to controls must be made on the UI thread. The simplest way is to simply invoke a method on the UI thread:
C#:
richTextBox1.Invoke(() => richTextBox1.AppendText(Convert.ToString(blurb)));
A more rigorous implementation might look like this:
C#:
private void AppendTextToRichTextBox1(string text)
{
    if (richTextBox1.InvokeRequired)
    {
        richTextBox1.Invoke(new Action<string>(AppendTextToRichTextBox1), text);
    }
    else
    {
        richTextBox1.AppendText(text);
    }
}
and then call:
C#:
AppendTextToRichTextBox1(Convert.ToString(blurb));
in your event handler.
 
Since I have the night to myself tonight, with some spare time, I will try to help you understand what is happening here, and give you some info along the way. On par with what John described above, I will give you another analogy to look at and test with so that you can understand why this happens and how you can fix it. The version i will demonstrate is slightly more extensive to what's posted above. I done it like this so you can understand what's happening in your own code. Take this example class for a form application :
C#:
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        private void Form1_Shown(object sender, EventArgs e)
        {
            void updateUI()
            {
                UpdateResponse();
            }
            new Thread(updateUI).Start();
        }
        public delegate void Callback(string text);

        private void UpdateResponse()
        {
            textBox1.Text = "foo";
        }
        private void UpdateMethod(string responsiveMsg)
        {
            textBox1.Text = responsiveMsg;
        }
When the form is shown (You need to add this event from the event properties), it launches UpdateResponse() in a new thread from void updateUI() which is run on a new non-ui thread.
C#:
        private void UpdateResponse()
        {
            textBox1.Text = "foo";
        }
Notice that you can not update the textbox1.Text property? That's because you are on a new thread that isn't a UI thread which textBox1.Text belongs to. - Hence you get :
System.InvalidOperationException: 'Cross-thread operation not valid: Control 'textBox1' accessed from a thread other than the thread it was created on.'
Note where I was operating my new thread, I will make an alteration to that code. Change this :
C#:
        private void UpdateResponse()
        {
            textBox1.Text = "foo";
        }
To this :
C#:
        private void UpdateResponse()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("My message");
            Invoke(new Callback(UpdateMethod), new object[] { (sb.ToString()) });
        }
All I have done is added a string builder, appended the string with the message I wish to update my UI with. And similar to John's example above, I will update that message with the following :
C#:
Invoke(new Callback(UpdateMethod), new object[] { (sb.ToString()) });
Invoke calls the Delegate : public delegate void Callback(string text); and passes method and the parameters to the method new Callback(UpdateMethod), new object[] { (sb.ToString()) }); which then essentially just lets us update the UI with the message we passed from the non-ui thread through the delegate back to our UI, which is what was done above in John's post. Though I think my example is more template friendly than John's brief example. However, if you are familiar with the language you could massively shorten this template above and use it as reusable code for communicating across threads.

Another thing you could do during debugging; which I highly advise against, unless absolutely certain you know what your code is actually doing when it executes, is to use CheckForIllegalCrossThreadCalls = false; Control.CheckForIllegalCrossThreadCalls Property (System.Windows.Forms) inside the private void UpdateResponse(). I am not a fan of doing this and regardless of my certainty or not, I never use it as I actually consider it bad practice, and I am sure others here would too.

It is also probably best, before you try my example code, to read this documentation in full and the relating articles as they are very insightful to dealing with threading related calls. How to: Make thread-safe calls to Windows Forms controls and Threads and threading

Not much point touching on much more regarding your code since John already did that, but its safe to say if you are getting that error on :
C#:
handler.OnBlurb += (IBlurb blurb) =>
            {
This would be the same situation as me trying to update :
C#:
        private void UpdateResponse()
        {
            textBox1.Text = "foo";
        }
Does this help you to understand what you need to do next?

Hope this helps.
 
Last edited:
Since I have the night to myself tonight, with some spare time
You know you're in deep when a night to yourself means writing code for strangers on the internet. ;)
 
Back
Top Bottom