Reputation: 3617
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
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.
Upvotes: 6
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
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