Why does setting a form's enabled property crash the application? - c#

private void launchbutton_Click(object sender, EventArgs e)
{
launchbutton.Enabled = false;
Process proc = new Process();
proc.EnableRaisingEvents = true;
proc.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
//The arguments/filename is set here, just removed for privacy.
proc.Exited += new EventHandler(procExit);
proc.Start();
}
private void procExit(object sender, EventArgs e)
{
MessageBox.Show("YAY","WOOT");
Thread.Sleep(2000);
launchbutton.Enabled = true;
}
2 Seconds after I quit the created process, my program crashes. Why?

You're modifying a winform control on a different thread than the one that created that control (the main UI thread). Winform controls are not thread-safe and typically will throw an exception if you modify their state from any thread other than the one that created it.
You can accomplish this using the InvokeRequired property and BeginInvoke method found on the Form or control object.
For example, something like this:
private void procExit(object sender, EventArgs e)
{
MessageBox.Show("YAY", "WOOT");
Thread.Sleep(2000);
// ProcessStatus is just a class I made up to demonstrate passing data back to the UI
processComplete(new ProcessStatus { Success = true });
}
private void processComplete(ProcessStatus status)
{
if (this.InvokeRequired)
{
// We are in the wrong thread! We need to use BeginInvoke in order to execute on the correct thread.
// create a delegate pointing back to this same function, passing in the same data
this.BeginInvoke(new Action<ProcessStatus>(this.processComplete), status);
}
else
{
// check status info
if (status.Success)
{
// handle success, if applicable
}
else
{
// handle failure, if applicable
}
// this line of code is now safe to execute, because the BeginInvoke method ensured that the correct thread was used to execute this code.
launchbutton.Enabled = true;
}
}

Related

Calling Process.WaitForExit() in button clicked event is blocking program

I have written same app using Windows Forms to run process after button was pushed and read its standard output. When I call method "test()" in button1_Click() my program is blocking. But when I call "test()" in "Form1" constructor everything work as expected. Where is problem?
using System;
using System.Windows.Forms;
namespace DISMassistant
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
process1.StartInfo.RedirectStandardError = true;
process1.StartInfo.RedirectStandardOutput = true;
process1.StartInfo.UseShellExecute = false;
process1.StartInfo.FileName = "cmd.exe";
process1.StartInfo.Arguments = "/?";
}
public void button1_Click(object sender, EventArgs e)
{
}
public void test()
{
process1.Start();
process1.BeginOutputReadLine();
process1.BeginErrorReadLine();
process1.WaitForExit();
process1.CancelOutputRead();
process1.CancelErrorRead();
process1.Close();
}
private void process1_OutputDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e)
{
if (string.IsNullOrEmpty(e.Data)) return;
richTextBox1.Text += e.Data + "\n";
}
private void process1_ErrorDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e)
{
if (string.IsNullOrEmpty(e.Data)) return;
richTextBox1.Text += e.Data + "\n";
}
}
}
Fixing all of the missing parts in your sample code, there's no issue (beyond, of course, that you wouldn't want to WaitForExit in a GUI application):
// Warning - The code below is WRONG! Awful even, since it will _appear_ to work in some cases.
void Main()
{
Application.Run(new Form1());
}
public class Form1 : Form
{
Process process1 = new Process();
Button button1;
RichTextBox richTextBox1;
public Form1()
{
button1 = new Button { Text = "Run" };
button1.Click += button1_Click;
Controls.Add(button1);
richTextBox1 = new RichTextBox { Left = 100 };
Controls.Add(richTextBox1);
process1.StartInfo.RedirectStandardError = true;
process1.StartInfo.RedirectStandardOutput = true;
process1.StartInfo.UseShellExecute = false;
process1.StartInfo.FileName = "cmd.exe";
process1.StartInfo.Arguments = "/?";
}
public void button1_Click(object sender, EventArgs e)
{
test();
}
public void test()
{
process1.Start();
process1.OutputDataReceived += process1_OutputDataReceived;
process1.ErrorDataReceived += process1_ErrorDataReceived;
process1.BeginOutputReadLine();
process1.BeginErrorReadLine();
process1.WaitForExit();
process1.CancelOutputRead();
process1.CancelErrorRead();
process1.Close();
}
private void process1_OutputDataReceived(object sender,
System.Diagnostics.DataReceivedEventArgs e)
{
if (string.IsNullOrEmpty(e.Data)) return;
richTextBox1.Text += e.Data + "\n";
}
private void process1_ErrorDataReceived(object sender,
System.Diagnostics.DataReceivedEventArgs e)
{
if (string.IsNullOrEmpty(e.Data)) return;
richTextBox1.Text += e.Data + "\n";
}
}
However, this is still something you do not want to do. GUI controls shouldn't be accessed from a different thread than they were created on. I'm not getting a problem in my test application, but even if it works, this is just disaster waiting to happen.
It so happens that RichTextBox has a bit weird handling of the Text property. If the handle isn't created yet, Text only changes a single field in the class. It doesn't do any multi-threaded access checking or anything. Now, when the handle actually gets created, the value from this field is applied to the text of the control for real - on the correct thread.
This is probably why you're seeing your weird behaviour. In the form constructor, the handle isn't created yet. The access from a background thread is still a bad idea, but it doesn't actually break anything, most of the time (you may lose some of the process output). When the form is shown, the handles are created, and the text box shows the "correct" output.
When the form is already created (and shown), this changes. On a single-threaded apartment thread, you're expected to handle multi-threading yourself. You're not supposed to access a control from a different thread - and most controls will call you out on it and throw an exception. For whatever reason, RichTextBox isn't (always) that kind of control. So as it happens, on STA, the code happens to work - not reliably or safely, but it doesn't hang, and it doesn't crash.
But your UI thread seems to be in a multi-threaded apartment. And in MTA, when you try to assign text to the RichTextBox (with a created handle), the message is marshalled to the UI thread. But your UI thread is busy waiting for the process to exit! Thus, you hang.
How do you solve this?
Never access GUI controls from a different thread than the one they were created on. Marshal the call explicitly (e.g. using Invoke), and you'll get reliable and consistent behaviour.
Do not block the UI thread. Process has an Exited even that allows you to react to the process exiting in an asynchronous way.

Access "RichTextBox" Component from another thread

I have a console application, that launches a form application (from another class) in a different thread.
But then, I wan't to access the richTextBox1 component from my main class, in the main thread and that throws an error wich says that I'm trying to access the component from another thread.
My code:
(Form application)
public partial class ChatGui : Form
{
public static RichTextBox textBox;
public ChatGui()
{
InitializeComponent();
richTextBox1.ReadOnly = true;
richTextBox1.BackColor = SystemColors.Window;
}
public void WriteLine(string line)
{
richTextBox1.Text += line+"\r\n";
}
private void textBox1_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
WriteLine("[You]: "+textBox1.Text);
NetworkManager.SendPacket("rchat_msg " + textBox1.Text.Replace(" ", "%20"));
textBox1.Text = "";
e.Handled = true;
}
}
public void Exit()
{
Application.Exit();
}
private void ChatGui_FormClosing(object sender, FormClosingEventArgs e)
{
e.Cancel = true;
}
}
The main console application
public static void StartRemoteChat()
{
RemoteChat = true;
Program.ChatGui = new ChatGui();
new Thread(new ThreadStart(() =>
{
Application.Run(Program.ChatGui);
while (RemoteChat)
{
// ...
}
})).Start();
}
So, how can I access the richTextBox1 component from my main thread (I want to change some variables of the component) without this error happening ?
The control is owned by the thread that creates it.
In your case the thread that you start owns the form because it paints it so its going to be your forms ui thread.
However when you try to use another thread to make changes to the control, it will throw an exception just like you said.
The way around this is to invoke the thread that created it to come and make the change that you want like this:
richTextBox1.BeginInvoke(new Action(()=>
{
// make changes to control here.
}));
one that can help you is the "CheckForIllegalCrossThreadCalls = false;" but used here(at the beginning):
public Form1()
{
InitializeComponent();
CheckForIllegalCrossThreadCalls = false;
}
the advice is to use each call to "RichTextBox1" the invoke method.
this method allows to interact with elements created by other threads.
example:
richTextBox1.Invoke(new Action(() => richTextBox1.Text = "hello word"));
"CheckForIllegalCrossThreadCalls = false" is a solution that has enough dangers if it is used in complex programs.
I hope I was helpful.
Simple call invoke to invoke the method from a new thread:
if (InvokeRequired)
{
Invoke(new Action(**METHODHERE**));
return;
}
and to pass parameters:
if (InvokeRequired)
{
Invoke(new Action<string>(**METHODHERE**) **parameter**);
return;
}
Good read: https://msdn.microsoft.com/en-us/library/ms171728(v=vs.110).aspx
try setting following property.
RichTextBox.CheckForIllegalCrossThreadCalls=false;
this may help you.

Unable to modify variables from separate thread

So I'm making a C# app which has to continuously read and display the contents of a text file, while allowing the user to enter something into a text box and append it to the end of that very file.
I'm doing this by running my read method on a separate thread, however changing the variable which stores the display text-files contents is what's causing a problem. Initially I tried having a method which did this, however that's not working and gave a 'cross-thread-operation-not-valid' error. I then tried applying some code I found on MSDN, but now after updating the variable once the thread ended!
Please help.
partial class MainForm
{
delegate void SetTextCallback(string text);
public static string msg;
public static string name;
public void InitClient()
{
name = "public.txt";
Console.WriteLine(name);
if(!File.Exists(name))
{
File.Create(name);
File.AppendAllText(name, "Welcome to " + name);
}
Thread Read = new Thread(new ThreadStart(this.Client));
Read.Start();
while(!Read.IsAlive);
}
public void WriteText()
{
File.AppendAllText(name, this.InputBox.Text);
this.InputBox.Clear();
}
private void SetText(string text)
{
if (this.OutPut.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
this.OutPut.Text = text;
}
}
public void Client()
{
msg = File.ReadAllText(name);
Console.WriteLine(msg);
Thread.Sleep(300);
this.SetText(msg);
}
}
Why is the thread behaving like this. How can I modify my code so that the contents of the output box always equals that of the text file.
Any suggestions welcome.
You've got multiple problems here,
the use of the File is probably not thread-safe.
your method does not repeat
your are Sleep()ing on a Thread
You can solve all of them by ditching the Thread and use a simple Timer.
Try using a background worker instead of creating a new thread. The background worker will run its content in a seperate thread, and allows you to report 'progress' while its working. This progress report will always be run on the UI-thread (or the thread which started the background worker).
It also has an event which is called when the background worker is finished. This is also run on the UI thread.
This example should get you started.
Update: Added some very basic error handling as suggested
The idea is to use the UserData (2nd argument) of ReportProgress to do updates on the UI thread whenever you need to. In this case it is a string, but this can be any object.
Furthermore, you can use the Result of the DoWorkEventArgs to produce a final result from the background work. In this case, I return any exception which was thrown, or null otherwise, but you can return whatever you want here as well.
It is, as Henk mentioned in his comment, very important to handle errors that occur inside the DoWork callback, because exceptions etc which occurs here will be swallowed and the worker will complete as if nothing bad happened.
private BackgroundWorker _backgroundWorker;
public Form1()
{
InitializeComponent();
_backgroundWorker = new BackgroundWorker();
_backgroundWorker.WorkerReportsProgress = true;
_backgroundWorker.WorkerSupportsCancellation = true;
// This is the background thread
_backgroundWorker.DoWork += BackgroundWorkerOnDoWork;
// Called when you report progress
_backgroundWorker.ProgressChanged += BackgroundWorkerOnProgressChanged;
// Called when the worker is done
_backgroundWorker.RunWorkerCompleted += BackgroundWorkerOnRunWorkerCompleted;
}
private void BackgroundWorkerOnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs runWorkerCompletedEventArgs)
{
if (runWorkerCompletedEventArgs.Result != null)
{
// Handle error or throw it
throw runWorkerCompletedEventArgs.Result as Exception;
}
textBox1.Text = "Worker completed";
}
private void BackgroundWorkerOnProgressChanged(object sender, ProgressChangedEventArgs progressChangedEventArgs)
{
textBox1.Text = progressChangedEventArgs.UserState as string;
}
private void BackgroundWorkerOnDoWork(object sender, DoWorkEventArgs doWorkEventArgs)
{
try
{
for (int i = 0; i < 100 && !_backgroundWorker.CancellationPending; i++)
{
_backgroundWorker.ReportProgress(0, i + " cycles");
Thread.Sleep(100);
}
}
catch (Exception ex)
{
doWorkEventArgs.Result = ex;
}
}
private void startButton_Click(object sender, EventArgs e)
{
if (!_backgroundWorker.IsBusy)
_backgroundWorker.RunWorkerAsync();
}
private void cancelButton_Click(object sender, EventArgs e)
{
if(_backgroundWorker.IsBusy)
_backgroundWorker.CancelAsync();
}

C# Backgroundworker: What way to periodically update a PictureBox?

I'm using a BackgroundWorker to periodically check a hardware switch. Due to it is connected via a slow RS485 network, I have to delay the next status update.
On switch Status change I want to update an OK/nOK Picture Box. This is realized as a green OK pictureBox over a nOK pictureBox. No real work is done here.
For expandability I decided to use the Backgroundworker. Finally I want to have a hidden worker, which
provides globally the Status of three switches and
updates on StatusChange the PictureBoxes.
Problem description
Once the BackgroundWorker is started, it works as expected. However the GUI freezes.
What did I try?
The MSDN BackgroundWorker Class Note 1
says, that GUI should be updated via ProgressChanged. I tried to raise this Event by Worker_Switch.ReportProgress(fakeProgress++) and failed. The PictureBox wasn't updated anymore.
Snippet from designer
this.Worker_Switch = new System.ComponentModel.BackgroundWorker();
//
// Worker_Switch
//
this.Worker_Switch.WorkerSupportsCancellation = true;
this.Worker_Switch.DoWork += new System.ComponentModel.DoWorkEventHandler(this.Worker_Switch_DoWork);
Snippet from Main Form
delegate void SetEventCallback(object sender, DoWorkEventArgs e); // Threadsafe calls for DoWork
private void btnBackgroundworker_Click(object sender, EventArgs e)
{
if (!Worker_Switch.IsBusy)
{
Worker_Switch.RunWorkerAsync();
}
}
private void Worker_Switch_DoWork(object sender, DoWorkEventArgs e)
{
// Worker Thread has no permission to change PictureBox "pictureBoxSwitchrightOK"
// Therefore this method calls itsself in the MainThread, if necessary.
while (!Worker_Switch.CancellationPending)
{
if (this.pictureBoxSwitchrightOK.InvokeRequired) // Worker Thread
{
System.Threading.Thread.Sleep(400);
SetEventCallback myCall = new SetEventCallback(Worker_Switch_DoWork);
this.Invoke(myCall, new object[] { sender, e });
}
else // Main Thread
{
// Turns OK Picture Box invisible, if nOk State (Switch pushed)
pictureBoxSwitchrightOK.Visible = SwitchOK("right"); // true: OK (green)
this.Refresh();
}
}
private bool SwitchOK(string rightOrLeft) // select one of the switches
{ (...)} // gets hardware switch status
Edit: Special Thanks to laszlokiss88 (3 possibilities) and JMK (for simplicity with System.Windows.Forms Timer from toolbox)
This alternative from Toolbox also worked:
this.timer_Switch.Enabled = true;
this.timer_Switch.Interval = 400;
this.timer_Switch.Tick += new System.EventHandler(this.timer_Switch_Tick);
private void timer_Switch_Tick(object sender, EventArgs e)
{
motorSwitchControl.Init(); // globally available Switch status
SwitchRight = SwitchOK("right");
SwitchRightOK.Visible = SwitchRight;
SwitchLeft = SwitchOK("left"); // globally available Switch status
SwitchLeftOK.Visible = SwitchLeft;
SwitchAllOK = SwitchRight & SwitchLeft;
this.Refresh();
}
a) Is it correct, that the Sleep() actually happens in the Worker Thread?
- no Main Thread
b) What is going wrong, if I manipulate user interface objects in DoWork? (Contrary to MSDN Note)
- works in Main Thread?
c) What is the correct way to periodically update a PictureBox? DoWork, ProgressChanged, RunWorkerCompleted...?
- Three possibilities from laszlokiss88 answer.
You can update the UI from the DoWork event via the Dispatcher, or Control.Begininvoke(winforms), or you can do it via the ProgressChanged event of the BackgroundWorker:
public MainWindow()
{
InitializeComponent();
var bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
bw.RunWorkerAsync();
}
void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// You are in the main thread
// Update the UI here
string data = (string)e.UserState;
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
// You are in a worker thread
(sender as BackgroundWorker).ReportProgress(0, "right");
}
For a start you should almost never have a need to put an active background thead to sleep. I am also not sure why you are constructing/defining the delegate this way, try some thing like
public delegate void UpdatePictureBox();
myDelegate = new UpdatePictureBox(UpdatePictureboxMethod);
then you have a method UpdatePictureBoxMethod
private void UpdatePictureBoxMethod()
{
this.pictureBox1.Image = Properties.Resources.SomeImage;
}
or something simalar, where you pass in the image to update to.
Alternatively you could use the (bgWorker as BackgroundWorker).ReportProgress(progress, object); method. So from the background thread you call
(bgWorker as BackgroundWorker).ReportProgress(progressBarValue, infoBall);
where here class IfoBall will hold all your important information
class InfoBall
{
public int nProgressBar { get; set; }
public int nMaxProgressBar { get; set; }
public Image image { get; set; }
}
then you can pass this object back to the UI thread and do your updates
void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// On UI thread.
InfoBall someBall = (InfoBall)e.UserState;
this.pictureBox1.Image = someBall.image;
// etc...
}
I hope this helps.

How to enable form button after process has exited?

I have an windows application developed using C#. In this application, I am creating one process. I want to enable and disable few buttons when Process_Exited() event occures.
In Process_Exited() method, I have written code to enable buttons but at runtime I get error as
"Cross-thread operation not valid:
Control
'tabPage_buttonStartExtraction'
accessed from a thread other than the
thread it was created on."
My code snippet is :
void rinxProcess_Exited(object sender, EventArgs e)
{
tabPage_buttonStartExtraction.Enabled = true;
tabPageExtraction_StopExtractionBtn.Enabled = false;
}
Can anyone suggest how to make this possible?
Move the enable/disable lines in a separate method and call that method from rinxProcess_Exited using Control.Invoke method.
You're attempting to change the UI from a different thread.
Try something like this;
private void SetText(string text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.textBox1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
this.textBox1.Text = text;
}
}
You shouldn't be doing much work on the UI from another thread, as the invocations are quite expensive.
Source: http://msdn.microsoft.com/en-us/library/ms171728.aspx
You must make UI changes on the UI thread. See this question for more details.
Here's the solution applied to your example:
void rinxProcess_Exited(object sender, EventArgs e)
{
if (this.InvokeRequired)
{
this.Invoke((Action)(() => ProcessExited()));
return;
}
ProcessExited();
}
private void ProcessExited()
{
tabPage_buttonStartExtraction.Enabled = true;
tabPageExtraction_StopExtractionBtn.Enabled = false;
}

Categories

Resources