Valuator
Valuator

Reputation: 3617

How is this code executing in a different thread?

In a Windows Forms project I have a handler for a button that opens a file in Notepad for editing. Once notepad closes I call a function RefreshTextBox() to parse the text file and update a TextBox based on a value. Here is the method that opens Notepad and calls the refresh method once its closed:

private void button_Click(object sender, EventArgs e)
    {
            Process p = new Process
            {
                EnableRaisingEvents = true,
                StartInfo =
                {
                    FileName = "NOTEPAD.EXE",
                    Arguments = _path,
                    WindowStyle = ProcessWindowStyle.Maximized,
                    CreateNoWindow = false
                }
            };

            p.Exited += (a, b) =>
            {
                RefreshTextBox();
                p.Dispose();
            };

            p.Start();
    }

And code to refresh the textbox:

private void RefreshTextBox()
    {
        using (StreamReader reader = File.OpenText(_appSettingsPath))
        {
            string text = reader.ReadToEnd();

            // Code to parse text looking for value...

            // InvalidOperationException thrown here:
            textBox.Text = reader.Value.ToString();
        }
    }

This throws an Exception for trying to update the Control from a thread other than the one it was created on. I'm having trouble understanding why though. I'm not doing this in a new task or backgroundworker or anything like that. Obviously notepad is running in another thread, but the refresh method isn't called until after it's process has exited.

Edit: I should add that this error throws up a Fatal Exception popup when debugging in Visual Studio (as an Admin). It doesn't show the popup when running the application on its own, either the exception is silently swallowed or it doesn't occur then.

Upvotes: 2

Views: 107

Answers (3)

sumeet kumar
sumeet kumar

Reputation: 2648

As per documentation if Process SynchronizingObject is not set it will execute exited event in system threadpool to avoid this and run that event handler in UI thread you need to set SynchronizingObject to Form Instance

When SynchronizingObject is null, methods that handle the Exited event are called on a thread from the system thread pool. For more information about system thread pools, see ThreadPool.

If you set

p.SynchronizingObject = WindowsFormName;

Then it will run in same thread or it will execute in a system threadpool thread which will cause crossthread exception.

MSDN Reference

Upvotes: 6

Dhejo
Dhejo

Reputation: 71

I would recommend capturing the synchronization context and posting the RefreshTextBox call onto it. Something like:

 private void button_Click(object sender, EventArgs e)
{
var _synchronizationContext = WindowsFormsSynchronizationContext.Current;
            Process p = new Process
            {
                EnableRaisingEvents = true,
                StartInfo =
                {
                    FileName = "NOTEPAD.EXE",
                    Arguments = _path,
                    WindowStyle = ProcessWindowStyle.Maximized,
                    CreateNoWindow = false
                }
            };

            p.Exited += (a, b) =>
            {
                _synchronizationContext.Post(_=> RefreshTextBox(), null);
                p.Dispose();
            };

            p.Start();
    }

Upvotes: 0

wdomains
wdomains

Reputation: 86

    private void button_Click(object sender, EventArgs e)
    {
        Process p = new Process
        {

            EnableRaisingEvents = true,
            StartInfo =
            {
                FileName = "NOTEPAD.EXE",
                Arguments = _path,
                WindowStyle = ProcessWindowStyle.Maximized,
                CreateNoWindow = false
            }
        };
        //p.SynchronizingObject = this;
        p.Exited += (a, b) =>
        {
            RefreshTextBox();
            p.Dispose();
        };

        p.Start();
    }
    private void RefreshTextBox()
    {
        using (StreamReader reader = File.OpenText(_appSettingsPath))
        {
            string text = reader.ReadToEnd();

            // Code to parse text looking for value...

            //textBox.Text = text; // reader.Value.ToString();
            threadSafeControlUpdate(textBox, text);
        }
    }
    public delegate void updateUIfunc(Control c, object v);
    public void threadSafeControlUpdate(Control c, object v)
    {
        if (this.InvokeRequired)
        {
            this.BeginInvoke(new updateUIfunc(threadSafeControlUpdate), c, v);
            return;
        }
        if (c is TextBox && v is string)
        {
            c.Text = (string)v;
        }
    }

Upvotes: 0

Related Questions