while the execution of a time consuming python script , i would manage the IU with background worker to display a progress bar.
i have used the background worker successfully when i needn't the event OutputDataReceived , but the script that i'm using prints some progress values like ("10" , "80",..), so i got to listen the event OutputDataReceived.
i get this error : This operation has already had OperationCompleted called on it and further calls are illegal. in this line progress.bw.ReportProgress(v);.
i tried to use 2 background worker instances, one executes and the other listens , it gives no errors but it seems do not call the event 'OutputDataReceived' so i don't see any progress in the progress bar.
below the code that i used:
private void execute_script()
{
progress.bw.DoWork += new DoWorkEventHandler( //progress.bw is reference to the background worker instance
delegate(object o, DoWorkEventArgs args)
{
System.Diagnostics.Process proc = new System.Diagnostics.Process();
proc.StartInfo.FileName = "python.exe";
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.Arguments = #".\scripts\script1.py " + file_path + " " + txtscale.Text;
//proc.StartInfo.CreateNoWindow = true;
//proc.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
proc.StartInfo.RedirectStandardOutput = true;
//proc.EnableRaisingEvents = true;
proc.StartInfo.RedirectStandardError = true;
proc.StartInfo.RedirectStandardError = true;
proc.OutputDataReceived += new System.Diagnostics.DataReceivedEventHandler(proc_OutputDataReceived);
proc.Start();
proc.BeginOutputReadLine();
//proc.WaitForExit();
//proc.Close();
});
progress.bw.RunWorkerAsync();
}
///the function called in the event OutputDataReceived
void proc_OutputDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e)
{
//throw new NotImplementedException();
if (e.Data != null)
{
int v = Convert.ToInt32(e.Data.ToString());
MessageBox.Show(v.ToString());
// report(v);
progress.bw.ReportProgress(v);
}
else
MessageBox.Show("null received");
}
The problem is that the BackgroundWorker's DoWork handler finishes as soon as the process starts, as there's nothing "waiting" (since you commented out proc.WaitForExit()) for the process to finish. Once the BackgroundWorker work handler completes, you can no longer report progress using that instance.
Since Process.Start is already asynchronous, there is no reason to use a background worker at all. You can just marshal the call from OutputDataReceived onto the UI thread yourself:
///the function called in the event OutputDataReceived
void proc_OutputDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e)
{
//throw new NotImplementedException();
if (e.Data != null)
{
int v = Convert.ToInt32(e.Data.ToString());
// MessageBox.Show(v.ToString());
// progress.bw.ReportProgress(v);
this.BeginInvoke( new Action( () => {
this.progressBar.Value = v;
}));
}
}
If you use this, don't create the BackgroundWorker at all.
BackGroundWorker has a ReportProgress option that is built just for this.
BackgroundWorker.ReportProgress Method (Int32, Object)
Related
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.
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
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;
}
}
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();
}
I have created a simple WPF project where on button click I create a separate thread with new window, and pass data to it. On application Exit I am trying to close safely that thread/window. However, I get occasionally the bellow exception, which causes application instabilities.
So my question is how to handle that situation gracefully. Thx
On line:
Dispatcher.Invoke(new Action(() =>
I have
A first chance exception of type 'System.Threading.ThreadAbortException' occurred in mscorlib.dll
Additional information: Thread was being aborted.
My View has the following code:
Constructor
public MyView(ConcurrentQueue<MyItem> actionReports, ManualResetEvent actionCompletedEvent, string actionName)
{
_actionReports = actionReports;
_actionCompletedEvent = actionCompletedEvent;
_actionName = actionName;
InitializeComponent();
DataContext = this;
this.Loaded += MyView_Loaded;
}
void MyView_Loaded(object sender, RoutedEventArgs e)
{
var worker = new BackgroundWorker();
worker.DoWork += (o, ea) =>
{
while (true)
{
if (_actionCompletedEvent.WaitOne(0))
{
// Issue
Dispatcher.Invoke(new Action(() =>
{
Close();
}));
Thread.Sleep(100);
}
while (!_actionReports.IsEmpty)
{
// Do some stuff
}
}
};
worker.RunWorkerAsync();
}
Initialize of Window
public WindowLauncher(ManualResetEvent actionCompletedEvent, ManualResetEvent reportWindowClosedEvent, string actionName)
{
_actionCompletedEvent = actionCompletedEvent;
_reportWindowClosedEvent = reportWindowClosedEvent;
_actionName = actionName;
Thread thread = new Thread(new ThreadStart(() =>
{
_reportWindow = new MyView(_messageQueue, _actionCompletedEvent, actionName);
_reportWindow.Show();
// InvokeShutdown to terminate the thread properly
_reportWindow.Closed += (sender, args) =>
{
_reportWindow.Dispatcher.InvokeShutdown();
};
_resetEvent.Set();
Dispatcher.Run();
}));
thread.Name = "MyWindowThread";
thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = true;
thread.Start();
}
I'd normally at least try to cancel the BackgroundWorker async action in the Window.OnClosing event and catch the pending cancellation. You'll still need to watch out for ThreadAbortExceptions but only if your async process is long-running.
private BackgroundWorker _worker;
private void MyView_Loaded(object sender, RoutedEventArgs e)
{
_worker = new BackgroundWorker();
_worker.WorkerSupportsCancellation = true;
_worker.DoWork += (o, ea) =>
{
while (true)
{
if (_actionCompletedEvent.WaitOne(0))
{
if (_worker.CancellationPending)
{
ea.Cancel = true;
return;
}
// Issue
Dispatcher.Invoke(new Action(() =>
{
Close();
}));
Thread.Sleep(100);
}
while (!_actionReports.IsEmpty)
{
// Do some stuff
}
}
};
}
protected override void OnClosing(CancelEventArgs e)
{
_worker.CancelAsync();
}
Background threads are aborted automatically by the runtime when your app exits. Normally they do not throw ThreadAbortExceptions (see Foreground and Background threads)
Invoking the dispatcher causes a method to be run on the dispatch thread, but since you used Invoke() rather than BeginInvoke(), the dispatcher needs to inform the background thread that the method finished. I suspect this is where the exception is being raised, but this is only conjecture on my part.
Try catching the exception like this:
try
{
Dispatcher.Invoke(new Action(() =>
{
Close();
}));
}
catch (ThreadAbortException)
{
Thread.ResetAbort();
}
Although this may not help if the exception is being raised on the dispatch thread. If this doesn't work then try BeginInvoke() instead.
BTW, you can probably reproduce this bug more easily if you stick some kind of delay in the start of your Invoked lambda and close the app at that point. That means the background thread will be aborted by the time the lambda completes.
I have a C# windows forms application that restores a database backup. After InitializeComponent() I call a GetServers() function that runs a cmd command "sqlcmd -L" and populates a drop down. This takes around 15-20 second to execute.
So when I run the application I have to wait 15-20 seconds before the main form appears. Is there a way to open the form first, and then run the GetServers() function?
private void GetServers()
{
System.Diagnostics.ProcessStartInfo procStartInfo =
new System.Diagnostics.ProcessStartInfo("cmd", "/c sqlcmd -L");
procStartInfo.RedirectStandardOutput = true;
procStartInfo.UseShellExecute = false;
procStartInfo.CreateNoWindow = true;
System.Diagnostics.Process proc = new System.Diagnostics.Process();
proc.StartInfo = procStartInfo;
proc.Start();
string processOutput = proc.StandardOutput.ReadToEnd();
var lines = processOutput.Split(
new[] { Environment.NewLine },
StringSplitOptions.RemoveEmptyEntries);
foreach (var line in lines)
{
if (!line.Trim().ToUpper().Contains("SERVERS:"))
cbxServer.Items.Add(line.Trim());
}
}
public Form1()
{
InitializeComponent();
GetServers();
}
This is a case where you could benefit from using a BackgroundWorker.
First, modify your GetServers method so that it returns the lines collection instead of processing it. Then you can do simething like this (pseudocode and probably not complete, but you get the idea):
public Form1()
{
InitializeComponent();
var worker = new BackgroundWorker();
worker.DoWork += (sender, e) => e.Result = GetServers();
worker.RunWorkerCompleted += (sender, e) =>
foreach (var line in (string[])e.Result) {/*Here you add the info to your form*/};
worker.RunWorkerAsync();
}
You can try to run your method in Form's Shown-event. That would still make the Form unresponsive until it's done with the GetServers(). The UI is always updated after all code on the UI-thead is executed.
You should check out BackgroundWorker and asyncronous methods.
For all too long, I have been trying to run an external .bat file (calls an R script for some statistical processing), and have the console redirect to the U.I.
I think I am close, but just as I have gotten it to work I have run into a sizable problem! That is: it only bloody works once the main thread has ended (via: return;), and not during Thread.Sleep, or .WaitOne() or etc.
Here is my code in the main thread.
string batLoc = ALLRG___.RSCRBIN_LOC + "current.bat";
BackgroundWorker watchboxdWorker1 = new BackgroundWorker();
watchboxdWorker1.DoWork += frmC.WatchboxWorker1_WatchExt;
frmC.wbResetEvent = new AutoResetEvent(false);
watchboxdWorker1.RunWorkerAsync(batLoc);
//Thread.Sleep(1000*20);
//frmC.wbResetEvent.WaitOne();
return;
Note the commented out Sleep and/or WaitOne() instructions. If I try and use these the BackgroundWorker DOES execute, but the 'events' which update the U.I do not.
The code in my form (frmC above) is as follows,
public void WatchboxWorker1_WatchExt(object sender, DoWorkEventArgs e)
{
string exeLoc = (string) e.Argument;
string arg1 = exeLoc;
string arg2 = "";
ProcessStartInfo pStartInfo = new ProcessStartInfo();
pStartInfo.FileName = exeLoc;
pStartInfo.Arguments = string.Format("\"{0}\" \"{1}\"", arg1, arg2);
pStartInfo.WorkingDirectory = Path.GetDirectoryName(exeLoc);
pStartInfo.CreateNoWindow = true;
pStartInfo.UseShellExecute = false;
pStartInfo.RedirectStandardInput = true;
pStartInfo.RedirectStandardOutput = true;
pStartInfo.RedirectStandardError = true;
Process process1 = new Process();
process1.EnableRaisingEvents = true;
process1.OutputDataReceived += new DataReceivedEventHandler(wbOutputHandler);
process1.ErrorDataReceived += new DataReceivedEventHandler(wbErrorHandler);
process1.StartInfo = pStartInfo;
process1.SynchronizingObject = rtbWatchbox;
process1.Start();
process1.BeginOutputReadLine();
process1.BeginErrorReadLine();
process1.StandardInput.Close();
process1.WaitForExit();
wbResetEvent.Set();
}
public void wbOutputHandler(Object source, DataReceivedEventArgs outLine)
{
int x = 0;
if (!String.IsNullOrEmpty(outLine.Data))
{
rtbWatchbox.AppendText(outLine.Data);
}
}
public void wbErrorHandler(Object source, DataReceivedEventArgs outLine)
{
int x = 0;
if (!String.IsNullOrEmpty(outLine.Data))
{
rtbWatchbox.AppendText(outLine.Data);
}
}
My problem is --
The wbOutputHandler and wbErrorHandler get fired as the console updates nicely - but only when the main thread has exited (using the return;).... if I use the Thread.Sleep or .WaitOne() in the main thread to pass control to the BackgroundWorker (WatchboxWorker1_WatchExt), then the code runs successfully, but the wbOutputHandler and wbErrorHandler methods do not get triggered at all.
In fact, if I do the Thread.Sleep(10*1000), then the external program starts running as planned, 10 seconds pass, then when the main UI thread exits I get a whole big enormous update all at once.
I don't want to have my main thread closed, I want to keep doing stuff there after the Worker is finished!
[ of course happy for alternate methods that are a better approach ]
"Help me Stack Overflow, you are my only hope!"
The answer was to put a backgroundWorker within another backgroundWorker, which is created for the UI Thread. I thought quite complicated given the reletivly simple requirement of printing a console output to the UI!
I now call my functions from the UI as follows -
private void btInsertBCModls_Click(object sender, EventArgs e)
{
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += RC2___Scratchpad4.BC_RunExistingBCModel;
bw.RunWorkerAsync(this);
}
Next I use the delegate & Invoke method on any richTextBox I need to update from another thread -
delegate void UpdateWriteboxCallback(String str);
public void wbWriteBox(string WriteString)
{
if (!String.IsNullOrEmpty(WriteString))
{
if (rtbWatchbox.InvokeRequired)
{
UpdateWriteboxCallback at = new UpdateWriteboxCallback(wbWriteBox);
this.Invoke(at, new object[] { WriteString });
}
else
{
// append richtextbox as required
}
}
}
Then from within my function I use another BackgroundWorker to run the console stuff -
public static void BC_RunExistingBCModel(object sender, DoWorkEventArgs e)
{
RC2___RhinegoldCoreForm frmC = e.Argument as RC2___RhinegoldCoreForm;
BackgroundWorker watchboxWorker = new BackgroundWorker();
watchboxWorker.DoWork += frmC.WatchboxWorker_RunProc;
watchboxWorker.RunWorkerAsync(batLoc);
while (watchboxWorker.IsBusy)
Thread.Sleep(50);
frmC.UpdateRGCoreStatusBox4("Executed script " + m + "... ");
}
Which in turn, in the DoWork function, calls the wbWriteBox function above.
public void WatchboxWorker_RunProc(object sender, DoWorkEventArgs e)
{
string exeLoc = (string) e.Argument;
string arg1 = exeLoc;
string arg2 = "";
ProcessStartInfo pStartInfo = new ProcessStartInfo();
pStartInfo.FileName = exeLoc;
pStartInfo.Arguments = string.Format("\"{0}\" \"{1}\"", arg1, arg2);
pStartInfo.WorkingDirectory = Path.GetDirectoryName(exeLoc);
pStartInfo.CreateNoWindow = true;
pStartInfo.UseShellExecute = false;
pStartInfo.RedirectStandardInput = true;
pStartInfo.RedirectStandardOutput = true;
pStartInfo.RedirectStandardError = true;
Process process1 = new Process();
process1.EnableRaisingEvents = true;
process1.OutputDataReceived += (s, e1) => this.wbWriteBox(e1.Data);
process1.ErrorDataReceived += (s, e1) => this.wbWriteBox(e1.Data);
process1.StartInfo = pStartInfo;
process1.SynchronizingObject = rtbWatchbox;
process1.Start();
process1.BeginOutputReadLine();
process1.BeginErrorReadLine();
process1.StandardInput.Close();
process1.WaitForExit();
//wbResetEvent.Set();
}
Phew! A tricky solution to an easily defined problem. If someone has a better way, let me know.
And thanks to Carsten for all the help - magnificent.
I have a C# application with a form window that has a button on it, I'll call myButton. I have an event attached to it, myButton_Click. What I want to do is disable the button and not allow any user interface with the button while the actions within the myButton_Click method are running.
Currently I launch another executable from within the myButton_Click method and like I said I do NOT want any user interaction while the other application is running. The problem I am running into is that even though the button is disabled, by myButton.Enabled == false;, if I click multiple times on the disabled button once I close the running application that was launched from within 'myButton_Click', the method 'myButton_Click' gets recalled as many times as I clicked on the disabled button previously.
In short I would like a way to make sure that no actions/button clicks are stored/accumulated while the outside application is running on the button that I disabled.
E.g.
private void myButton_Click(object sender, EventArgs e)
{
myButton.Enabled = false;
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.CreateNoWindow = false;
startInfo.UseShellExecute = false;
startInfo.FileName = "someapplication.EXE";
try
{
using (Process exeProcess = Process.Start(startInfo))
{
exeProcess.WaitForExit();
}
}
catch{// Log error.
}
myButton.Enabled = true;// turn the button back on
}
Thanks
The UI thread is being blocked by your external EXE and it's queuing up those click events. The proper solution is to use a background worker like this:
private void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += (doWorkSender, doWorkArgs) =>
{
// You will want to call your external app here...
Thread.Sleep(5000);
};
worker.RunWorkerCompleted += (completedSender, completedArgs) =>
{
button1.Enabled = true;
};
worker.RunWorkerAsync();
}
I think using async/await can give a cleaner solution
async private void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
await RunProcessAsync("notepad.exe");
button1.Enabled = true;
}
public Task RunProcessAsync(string processName)
{
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.CreateNoWindow = false;
startInfo.UseShellExecute = false;
startInfo.FileName = processName;
var proc = Process.Start(startInfo);
proc.EnableRaisingEvents = true;
proc.Exited += (s,e) => tcs.TrySetResult(null);
return tcs.Task;
}
I'm surprised that you're even getting that problem. It could possibly be that the windows message is not disabling the button quick enough. You could try putting a DoEvents() instruction after myButton.Enabled = false;