Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why we cant update UI from worker thread? same as other variables/object

Feel like asking a stupid question but I want to know answer. I don’t need any code to update UI from worker thread I know how to update UI from worker/thread pool.

I want to know that why we get this error “Cross-thread operation not valid: Control '' accessed from a thread other than the thread it was created on.” whenever any worker thread try to update UI controls? and Why not this error come when worker thread access object created by Main thread and there is no UI interaction?

See below example

public partial class Form1 : Form
{
    private void Form1_Load(object sender, EventArgs e)
    {
    }

    TextBox textbox2 = null;
    List<int> collection = new List<int>();
    public Form1()
    {
        InitializeComponent();
        textbox2 = new TextBox();
    }
    public void UpdateTextBox()
    {
        collection.Add(Thread.CurrentThread.ManagedThreadId);
        textbox2.Text = "hi, ThreadId: " + Thread.CurrentThread.ManagedThreadId.ToString();
        panel1.Controls.Add(textbox2);//If remove this line... will work with worker thread.
    }
    private void btnMainThread_Click(object sender, EventArgs e)
    {
        UpdateTextBox();
    }
    private void btnWorkerThread_Click(object sender, EventArgs e)
    {
        Thread t1 = new Thread(UpdateTextBox);
        t1.Start();//Will get error. why?
    }
}

How we can apply same kind of restruction for my "collection" variable.?

like image 890
Pankaj Rawat Avatar asked Jun 11 '17 10:06

Pankaj Rawat


1 Answers

The answer -I think - you are looking for (why and how the exception is raised) is because there is explicit code inside the Control class's Handle property that will check if the code that wants to access the property is running on the same thread that created the Control (the User-interface thread).

You can check the reference source for the Handle property here. The actual thread check occurs inside the implementation of the InvokeRequired property, that you can also check here.

Early versions of the .net Framework did not include this check, so it was very easy to access the user interface from a different thread. The reason why we can't shouldn't do it is because there is a great portion of the Win32 API code base that is not thread-safe, so calling it from a single thread is the only way to guarantee no concurrency problems will happen in a multi-threaded application.

The Handle property will be accessed before the control internally calls any related Win32 API function that interacts with it (you can think of the handle as the 'this' reference inside the WIN32 API), so it was a very good candidate for centralizing the cross-thread check.

If I understand correctly from your comments, you want to simulate the same behavior (to raise an exception) when a worker thread tries to update a certain value (not necessarily a user interface element) 'owned' by a different thread. In this case you could adopt the same strategy that the framework is using.

If you analyze the code for InvokeRequired, it is simply comparing the current thread's ID against the value returned by SafeNativeMethods.GetWindowThreadProcessId() and returning true if there is a mismatch. This causes the cross-thread exception to be raised in the Handle property's getter accessor. In your case you could store the ID of the thread that is allowed to access the variable or resource and manually raise the exception if there is a mismatch between this stored ID and the ID of the thread attempting to access the guarded value.

like image 193
BlueStrat Avatar answered Sep 27 '22 00:09

BlueStrat