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.
Related
I'm trying to implement a BackgroundWorker for monitoring a FileSystemWatcher service.
My code is divided as:
A Classes.cs wich contains all methods , variables and FileSystemWatcher implementation. And the main Form1 , wich contains form data and calls for the buttons\etc. When i run my program all that happens is the cursor to change (this was already expected) - the action happens in the background (things get done) but no report is shown on my progress bar. I got the example from a website and adapted it to my code - is there anything wrong i'm doing ? I believe there's something involved with the fact the only thing i call is the filesystemwatcher - but i expected that it would report the progress based on the action running "on background".
Any help is appreciated. Thanks
My form1 code (the BackgroundWorker part) and the FileSystemWatcher follows:
namespace PPF_Converter_v10
{
public partial class Form1 : Form
{
private FileManipulation prg;
//private FileManipulation FileOp;
public Form1()
{
InitializeComponent();
//FileOp = new FileManipulation();
prg = new FileManipulation();
//Load config before the program begins - loading sample config or newly generated config
prg.LoadConfig();
FillTextBox();
bgWorker.DoWork += new DoWorkEventHandler(bgWorker_DoWork);
}
BackgroundWorker CODE:
private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
if (!textBox1.Text.Contains("\\"))
{
MessageBox.Show("Please define the input folder before starting");
}
else if (!textBox2.Text.Contains("\\"))
{
MessageBox.Show("Please define the XML Output folder before starting");
}
else if (!textBox3.Text.Contains("\\"))
{
MessageBox.Show("Please define the Converted PPF Output Folder before starting");
}
else if (!textBox4.Text.Contains("\\"))
{
MessageBox.Show("Please define the Invalid PPF Output Folder before starting");
}
else
{
// calls the watcher
// prg.FileWatcher.SynchronizingObject = progressBar1.
prg.ProgramProcessing(textBox1.Text);
}
// do some long-winded process here
// this is executed in a separate thread
int maxOps = 1000000;
for (int i = 0; i < maxOps; i++)
{
//rtbText.AppendText(i.ToString() + "\r\n");
// report progress as a percentage complete
bgWorker.WorkerReportsProgress = true;
bgWorker.ReportProgress(100 * i / maxOps);
}
}
private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// update the progress bar
pbProgress.Value = e.ProgressPercentage;
}
private void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// return to "normal" mode of operation
this.Cursor = Cursors.Default;
btnGo.Enabled = true;
}
private void btnGo_Click_1(object sender, EventArgs e)
{
// give the appearance of something happening
this.Cursor = Cursors.WaitCursor;
btnGo.Enabled = false;
// call RunWorkerAsync to start the background thread
bgWorker.RunWorkerAsync();
}
Exception thrown when the RichtextBox is enabled:
Additional information: Cross-thread operation not valid: Control 'rtbText' accessed from a thread other than the thread it was created on.
You're invoking a MessageBox from the background thread on the foreground thread. That is like doing UI in two separate threads which is a no-no.
What you can do is either use events or an event aggregator from your background thread. I would probably go for the latter. This way, your background thread, when something is wrong, can (and should) abort immediately and notify through a message that it was unable to process the file.
Think of a background task as something that has no UI whatsoever. It is there in the background and can only communicate to the UI thread using events or messages.
I'm getting some troubles with my Winforms C# app.
I wish to make form named Popup closing after some operations in main thread are done. The problem is an exception caused by cross-thread form closing.
private void loginButton_Click(object sender, EventArgs e)
{
LoginProcess.Start(); // Running Form.show() in new thread
ActiveAcc.IsValid = false;
ActiveAcc.Username = userBox.Text;
try
{
LoginCheck(userBox.Text, passBox.Text);
}
catch (IOException)
{
MessageBox.Show("..");
return;
}
catch (SocketException)
{
MessageBox.Show("..");
return;
}
if (ActiveAcc.IsValid)
{
MessageBox.Show("..");
Close();
}
else
{
Popup.Close(); // Error caused by closing form from different thread
MessageBox.Show("");
}
}
public Login() // 'Main' form constructor
{
InitializeComponent();
ActiveAcc = new Account();
Popup = new LoginWaiter();
LoginProcess = new Thread(Popup.Show); //Popup is an ordinary Form
}
I've been trying to use various tools such as LoginProcess.Abort() or Popup.Dispose() to make it work properly, but even if app is working on runtime environment its still unstable due to Exceptions which are thrown.
I would be grateful for any help, and I am sorry for ambiguities in issue describing.
Why don't you let the UI thread do UI stuff like opening and closing Forms, and spawn the other thread (or background worker, or async task) to do the other stuff?
IMO, having other threads attempt to interact with elements on the UI thread (e.g., have a background thread directly set the text of a label or some such) is asking for heartache.
If you simply must keep your code as is, here is a fairly simple thing you could do. In Popup, add a static bool that defaults to true. Also in Popup, add a timer task that once every X milliseconds checks the status of that boolean. If it finds that the value has been set to false, let Popup tell itself to close within that timer tick.
I'm not crazy about this design, but it could look something like:
public partial class Popup : Form
{
public static bool StayVisible { get; set; }
private System.Windows.Forms.Timer timer1;
public Popup()
{
StayVisible = true;
this.timer1.Interval = 1000;
this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
InitializeComponent();
}
private void timer1_Tick(object sender, EventArgs e)
{
if (!StayVisible) this.Close();
}
}
Then, from another thread, when you want Popup to close, call
Popup.StayVisible = false;
Better yet, you would fire an event that Popup would receive so that it could close itself. Since you intend to use multiple threads, you'll have to deal with raising events cross-thread.
Why does my first attempt to change a button's text in this code not work, while the third attempt does work ?
My user will have to wait a few seconds while the serial port connects. After that, I want to alert him that he has already connected (a second attempt can cause problems).
I wanted to let him know that things are okay, so he won't think "duh" and click twice.
Fail. The text change never appears.
Okay, why does the third change in button text work, but the first one does not ? I don't know if the second one works or not.
/***********************************************************************
* Button: Connect Port *
***********************************************************************/
private void TheConnectPortButton_Click(object sender, EventArgs e)
{
string OldText = TheConnectPortButton.Text;
TheConnectPortButton.Text = "Busy, Please Wait"; /////// This never happens
ButtonBoss.ButtonHandler_ConnectPort();
TheConnectPortButton.Text = OldText; /////// Not sure if this happens
if (aUartSemaphoreThatTells.IfWeHaveConnectedToAPort == (int)aValueWhichIndicatesThat.YesWeHaveAGoodPortConnected)
{
TheConnectPortButton.Text = "Connected"; /////// This one does happen
}
}
the aUartSemaphoreThatTells.IfWeHaveConnectedToAPort is also used by the ButtonBoss routine to make sure he doesn't connect a second time, along with other button routines (e.g., make sure we are connected before we Tx/Rx or whatever).
I tried changing the code after the routine returns to look like this...
if (aUartSemaphoreThatTells.IfWeHaveConnectedToAPort == (int)aValueWhichIndicatesThat.YesWeHaveAGoodPortConnected)
{
TheConnectPortButton.Text = "Connected";
}
else
{
TheConnectPortButton.Text = OldText;
}
...and I still get the same result.
My guess (and that's all it is) is that threading is somehow involved in all this, and that the serial port routines trump the button text changing routines by some convolution that I don't follow properly at the moment.
Question: What do I need to do to get the text to change before the connection stuff hogs the system ?
(If that's what's happening)
Question 2: If I can't make this happen, I think I've read about "greying out" the buttons, or, I believe I saw somewhere that I can actually make a button go away right before the user's eyes so that he can't click it again. Links to example code would be welcome.
The problem is you're doing everything from one and the same event-handler consequently, so that the button has no time to get updated (redrawn). You could call Application.DoEvents(); method, but it's not a good idea at all, please, read Use of Application.DoEvents()
I think usually you're expected to push a time-consuming task into a separate thread, get progress report from it and update your GUI. There is a plenty of ways to create a "worker" thread and get some respond from it. For example, use a BackgroundWorker Class:
public partial class Form1 : Form
{
public Form1() { InitializeComponent(); }
private void button1_Click(object sender, EventArgs e)
{
BackgroundWorker w = new BackgroundWorker();
w.WorkerReportsProgress = true;
w.DoWork += new DoWorkEventHandler(w_DoWork);
w.ProgressChanged += new ProgressChangedEventHandler(w_ProgressChanged);
w.RunWorkerCompleted += new RunWorkerCompletedEventHandler(w_RunWorkerCompleted);
w.RunWorkerAsync();
button1.Text = "Started";
}
//may influence GUI, as this event handler is run on the GUI thread
void w_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
button1.Text = "Job is done";
}
//may influence GUI, as this event handler is run on the GUI thread
void w_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
button1.Text = e.ProgressPercentage.ToString();
}
//runs in the worker thread...should do the actual job
//may influence GUI through `ReportProgress`
//or through `Invoke` method
void w_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 1; i <= 10; i++)
{
Thread.Sleep(500);
worker.ReportProgress(10 * i);
}
}
}
Or you may use Task Class:
public partial class Form1 : Form
{
public Form1() { InitializeComponent(); }
private void button1_Click(object sender, EventArgs e)
{
new Task(DoTask).Start();
}
void DoTask()
{
for (int i = 1; i <= 10; i++)
{
Thread.Sleep(500);
//here you need to update GUI through `Invoke` method
//as the GUI may only be influenced from the the thread,
//where it's created
this.Invoke(new Action<int>((j) =>
{
button1.Text = j.ToString();
}), 10 * i);
}
}
}
Background
From the valuable advice I received here I have now moved all of my database intensive code to a backgroundworker, specifically the direct calls to the database. That code is executed during the backgroundworker's DoWork event. If a DataTable is returned during the DoWork event, I set that DataTable to a class-wide variable. This is done, to avoid having to invoke the controls requiring the DataTable every time I run this code.
While that code is being executed, I have a label that is updated in the main UI thread, to let the user know that something is occurring. To update the label I use a timer, such that every 750 ms a "." is appended to the label's string.
The first thing that I noticed was that the backgroundworker's RunWorkerCompleted event wasn't triggering. To solve this I did an Application.DoEvents(); before each call I made to the backgroundworker. It was ugly, but it caused the event to trigger. If anyone has an alternative to fix this, I am all ears.
I then came across an interesting predicament. If I run the program within Visual Studio 2010, in the debugging mode, I get an InvalidOperationException error stating that the "Cross-thread operation not valid: Control 'lblStatus' accessed from a thread other than the thread it was created on." This error occurs during the backgroundworker's RunWorkerCompleted event, where I set the text of a label in the main UI thread. But, when I launch the application directly, through the executable, it works exactly as desired (i.e. the label's text is set correctly).
Question
Can anyone explain what is going on / offer advice on how to improve upon this?
Code
I can't post all of the code involved, but here's some relevant stuff:
namespace Test
{
public partial class frmMain : Form
{
public static Boolean bStatus = false;
static Boolean bTimer = false;
System.Timers.Timer MyTimer = new System.Timers.Timer();
public frmMain()
{
InitializeComponent();
MyTimer.Elapsed += new System.Timers.ElapsedEventHandler(MyTimer_Elapsed);
MyTimer.Interval = 750; // Every 3/4 of a second
ExampleTrigger();
}
/// <Insert>Lots of unshown code here</Insert>
private void ExampleTrigger()
{
// This is used to simulate an event that would require the backgroundworker
Application.DoEvents();
bgw.RunWorkerAsync(0);
WaitText("Example - 1");
}
private static void MyTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
bTimer = true;
}
// Update status text
private void WaitText(string txt)
{
MyTimer.Enabled = true;
lblStatus.Text = txt;
bStatus = false;
while (!bStatus)
{
if (bTimer)
{
txt = txt + ".";
lblStatus.Text = txt;
lblStatus.Update();
bTimer = false;
}
}
MyTimer.Enabled = false;
}
private void bgw_DoWork(object sender, DoWorkEventArgs e)
{
int iSelect = (int)e.Argument;
switch (iSelect)
{
case 0:
// Hit the database
break;
/// <Insert>Other cases here</Insert>
default:
// Do something magical!
break;
}
}
private void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
bStatus = true;
lblStatus.Text = "Ready!"; // This is where the exception occurs!
}
}
}
Never run a while() loop like that in the UI thread.
You're freezing the UI until the loop terminates; this defeats the purpose.
In addition, System.Timers.Timer doesn't run callbacks in the UI thread.
Use a WinForms Timer instead.
Once you switch to a WinForms timer, you can simply append to the label inside the timer callback, and disable the timer when the operation finishes.
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;
}
}