Winform progress bar only updating when process is finished (C#, Python) - c#

Using a C# Winform as GUI to run my Python script in the background. Currently am applying a backgroundworker to handle it in another thread and keep the GUI responsive; however, the progress bar only seems to update when the process is finished. Stays at Value=0 and then changes to Value=100 once the process terminates; would much prefer have it increase as python executes the background process so I know it's still alive and working..
My understanding is that blocking should not be occurring because the backgroundworker is moving the process to a separate thread, so I'm not too sure what could be stopping it from updating.
Here's the relevant sections of my C# code:
public Form1()
{
InitializeComponent();
backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
}
----
private void button_test2_Click(object sender, EventArgs e) // this is the active button
{
if (backgroundWorker1.IsBusy != true)
{
backgroundWorker1.RunWorkerAsync();
}
}
----
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
if (backgroundWorker1.CancellationPending == true)
{
e.Cancel = true;
//break;
}
else
{
// Cutting out some stuff here for brevity. Just assigns arg1-4 based on inputs from the GUI, nothing crazy
// Python script to run
process.StartInfo.FileName = #"C:\blablabla\python.exe";
string script = #"C:\paths\pythonscriptgoeshere.py";
process.StartInfo.Arguments = $"\"{script}\" \"{arg1}\" \"{arg2}\" \"{arg3}\" \"{arg4}\"";
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.CreateNoWindow = true;
process.Start();
while (!process.StandardOutput.EndOfStream)
{
bool ItsAnInt = Int32.TryParse(process.StandardOutput.ReadLine(),out int PyOutput);
if (ItsAnInt) // I know this part is ugly lol - just making sure only numbers/percentages are being passed
{
backgroundWorker1.ReportProgress(PyOutput);
// PyOut is just a percentage (0-100) of how many iterations it has completed out of the total
}
}
//string stderrx = process.StandardError.ReadToEnd();
//string stdoutx = process.StandardOutput.ReadToEnd();
//MessageBox.Show($"Output: {stdoutx}\nError: {stderrx}");
}
---
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
Not quite sure why the progressbar1.Value isn't being updated from backgroundWorker1_ProgressChanged since it seems to be called while the process is actively running. Anyway, sorry for the long post and thanks for any help.
Cheers!
EDIT: Thank you #Jimi for helping me figure this out in the comments. The issue was that the UI was flooded with request since the test data set I was using was computed faster than the progressbar could update so it essentially didn't get a chance to breathe and catch up. Solution was to add in a Thread.Sleep(1) every handful of iterations to let it update.
EDIT 2: The plot thickens! Turns out that the progress bar would give me updates like I wanted, but wouldn't start the processes of updating until the background process was already done. Turns out what I needed to do was make a second background worker as well as an external python script that would read outputs and then report them to the bar. Not the best solution I'm sure but it seemed to do the trick for me :-)

Related

Run .exe processes in parallel and update progress bar in .NET 4.5

I have a dataGridView where I run a Process for each entry
and then update a toolStripProgressBar based on the output from the Process.
I have looked at the following threads,
Run two async tasks in parallel and collect results in .NET 4.5
Progress bar in parallel loop invocation
how to update the progress bar from tasks running concurrently
but I am not sure how to change my current code to something along these lines.
The main difference from these threads, as I see it, is that my computations are done by an outside application,
which I then need to collect the output from.
I guess I have to define each Process as an async task and then somehow collect the output.
For simplicity the processes are equal weighted in the sample code.
private iNumProcesses;
private void RunApps()
{
iNumProcesses = dataGridView1.Rows.Count;
string sPath = .exe application path
for (int i = 0; i < iNumProcesses; i++)
{
string sArgs = dataGridView1.Rows[i]["Arguments"].ToString();
ExecuteProgram(sPath, sArgs);
}
}
private void ExecuteProgram(string sProcessName, string sArgs)
{
using (cmd = new Process())
{
cmd.StartInfo.FileName = sProcessName;
cmd.StartInfo.Arguments = sArgs;
cmd.StartInfo.UseShellExecute = false;
cmd.StartInfo.CreateNoWindow = true;
cmd.StartInfo.ErrorDialog = true;
cmd.StartInfo.RedirectStandardOutput = true;
cmd.StartInfo.RedirectStandardError = true;
cmd.OutputDataReceived += new DataReceivedEventHandler(SortOutputHandler);
cmd.ErrorDataReceived += new DataReceivedEventHandler(SortOutputHandler);
cmd.Start();
cmd.BeginOutputReadLine();
while (!cmd.HasExited) { Application.DoEvents(); }
}
}
private void SortOutputHandler(object sender, DataReceivedEventArgs e)
{
Trace.WriteLine(e.Data);
this.BeginInvoke(new MethodInvoker(() =>
{
if (e.Data == "Start") { do something... }
else if (e.Data == "Finish") { do something... }
else if (e.Data == "End") { do something... }
else
{
// .exe application output numbers 1 through 100
toolStripProgressBar1.Value += Math.Round(Convert.ToInt32(e.Data)/iNumProcesses,0);
}
}));
}
How can I run the processes in parallel and update the progress bar
based on the output numbers 1 through 100 I get from the .exe applications?
Any advice or suggestions will be greatly appreciated.
My answer to this question is not particularly associated with "C#" and therefore I'm not going to speak directly in those terms: this unexpectedly-thorny issue is actually universal.
The first thing that you must do is to arrange to periodically update the user display. To avoid nasty race-conditions and other problems, you should have "the main thread" perform this task, driven by a millisecond timer. The thread consults a shared array of progress-information and updates all of the progress-bars accordingly. (If you want to use an event to avoid outright "timed waiting," feel free to do so.)
Since each of the launched child processes will have their own input and output streams, and will be writing to those streams asynchronously, you will find it necessary to spawn a "mommy thread" within your application to supervise each child. This thread corresponds to a particular external process, and, in fact, is the one that launches it. The thread continues to exist until it determines that the process that it launched has died.
The "mommy thread" observes the output-stream(s) of its appointed ward to determine its "progress." It updates the progress-variables in the shared array accordingly. (A key element of this design is that each "mommy thread" is able to pay 100% of its attention to just its child. And, because all the "mommies" are threads, they can easily share information with the thread that's updating those progress bars.)
Do you actually have to use semaphores-and-such to coordinate access between the mommy-threads and the main? Depending of course upon the exact internal implementation of that data-structure, the answer just might be, "probably not." (Oh, but it's probably safest to do it anyway.)

C# executing code for GUI before executing blocking processes

I have (hopefully) a straight forward question. I have a function that runs a command prompt command in a hidden window and returns the response in a string. This process takes about 3 seconds. I wanted to add a simple label in my GUI that would appear before the function executes. The label just states that something is being checked so the user does not think the interface is just slow or unresponsive.
Here is an example snippet to illustrate.
svnPathCheck_lbl.Visible = true; //Show the label
// Check validity of SVN Path
string svnValidity = getCMDOutput("svn info " + SVNPath_txtbox.Text);
// Here we call Regex.Match. If there is a 'Revision:' string, it was successful
Match match = Regex.Match(svnValidity, #"Revision:\s+([0-9]+)", RegexOptions.IgnoreCase);
svnPathCheck_lbl.Visible = false; //Hide the label
The getCMDOutput() function runs the hidden command and blocks the GUI.
What I expected this to do was display my label "Checking ...", then run the blocking function getCMDOutput(). Once the function returned and the GUI was responsive again, it would hide the label.
Instead, I never see the label show up at all. Its almost like it never executed. Could it be that the blocking function executes before the GUI has a chance to update?
Thanks for the help!
try this code, it should work...
private void button1_Click(object sender, EventArgs e)
{
svnPathCheck_lbl.Text = "Checking...";
svnPathCheck_lbl.Visible = true;
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += bw_DoWork;
bw.RunWorkerCompleted += bw_WorkCompleted;
bw.RunWorkerAsync();
}
private void bw_WorkCompleted(object sender, RunWorkerCompletedEventArgs e)
{
svnPathCheck_lbl.Text = "Work completed";
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
string svnValidity = getCMDOutput("svn info " + SVNPath_txtbox.Text);
Match match = Regex.Match(svnValidity, #"Revision:\s+([0-9]+)", RegexOptions.IgnoreCase);
}
I recommend that you run your getCMDOutput method asynchronously.
If this is a windows forms application you can do this using a BackgroundWorker. Handle the event DoWork of the worker to call your method, and where you were calling it previously, put instead backgroundWorker1.RunWorkerAsync()
This will cause the method to run in a new thead, so the UI updates will be treated separately and will be instantaneous.
try this:
svnPathCheck_lbl.Visible = true; //Show the label
Task connectToSVN = new Task(() => { this.connectToSVN; }); connectToSVN.Start(); //Open new Task to complite the code without blocking the GUI.
private void connectToSVN
{
// Check validity of SVN Path
string svnValidity = getCMDOutput("svn info " + SVNPath_txtbox.Text);
// Here we call Regex.Match. If there is a 'Revision:' string, it was successful
Match match = Regex.Match(svnValidity, #"Revision:\s+([0-9]+)", RegexOptions.IgnoreCase);
this.Dispatcher.Invoke((Action)(() =>
{
svnPathCheck_lbl.Visible = false; //Hide the label
}
));
}

Executing a method simultaneously to program window and stop it anytime

I need a functionality that will allow to execute method in a background and leave window responsive, but I need to have a possibility to stop or suspend it anytime. I know that threads are partly answer to my question, but unfortunately there is no way to stop thread from executing a time-absorbing block of code just like that. I had thoughts about process communication, but is it a good idea? Or maybe there is a way to terminate a thread unconditionally?
The only option that you have, if it's important that you can always stop the code at any point in it's execution, and when you can't have cooperative cancellation on the part of the worker, then you need to have a separate process. It is the most reliable way of stopping the execution of code in the manor you've described. It cannot be reliably done using Threads.
It seems that you're looking for BackgroundWorker .
You'll have to check in the second thread if the main thread is asking it to stop, and do so if needed.
Simple (tested) example:
public partial class Form1 : Form
{
BackgroundWorker w = new BackgroundWorker();
public Form1()
{
InitializeComponent();
w.WorkerSupportsCancellation = true;
w.DoWork += new DoWorkEventHandler(w_DoWork);
w.RunWorkerAsync();
}
void w_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 10000000000; i++)
{
if (w.CancellationPending)
{
MessageBox.Show("Cancelled");
break;
}
//Do things...
}
}
private void button1_Click(object sender, EventArgs e)
{
w.CancelAsync();
}
}
EDIT
If you're speaking of an HTTP request, perhaps: HttpWebRequest.Abort? (Though see this answer.)
As stated in comments thread.Abort() is an option, but not advised. Rather you should be using thread.Interrupt() and the reasons for that are well detailed here.
The reason you should not kill a thread instantly is because it could cause a lock being set, but never unset due to the fact that the code was suddenly stopped with no path out. So if it locks code that you will need to reuse, there would be no way to unlock it from the previous call. Obviously, you could build around this, but I'm assuming you are using blocking code that isn't built from the ground up by you.
You could do it in a separate process and kill the process with much less risk, but then passing the data back and forth and the added mess becomes complicated.
Here is an example of using an external process to do this chuck and being able to kill the process will less risk.
public class Main
{
public Main()
{
//In use
DoCalculation calc = new DoCalculation();
calc.StartCalculation(123, 188, ReceivedResults);
//Cancel after 1sec
Thread.Sleep(1000);
calc.CancelProcess();
}
public void ReceivedResults(string result)
{
Console.WriteLine(result);
}
}
public class DoCalculation
{
private System.Diagnostics.Process process = new System.Diagnostics.Process();
private Action<string> callbackEvent;
public void StartCalculation(int var1, int var2, Action<string> CallbackMethod)
{
callbackEvent = CallbackMethod;
string argument = "-v1 " + var1 + " -v2 " + var2;
//this is going to run a separate process that you'll have to make and
//you'll need to pass in the argument via command line args.
RunProcess("calcProc.exe", argument);
}
public void RunProcess(string FileName, string Arguments)
{
SecurityPermission SP = new SecurityPermission(SecurityPermissionFlag.Execution);
SP.Assert();
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.FileName = FileName;
process.StartInfo.Arguments = Arguments;
process.StartInfo.WorkingDirectory = "";
process.OutputDataReceived += ProcessCompleted;
process.Start();
}
public void CancelProcess()
{
if (process != null)
process.Kill();
}
private void ProcessCompleted(object sender, DataReceivedEventArgs e)
{
string result = e.Data;
if (callbackEvent != null)
{
callbackEvent.Invoke(result);
}
}
}
Can you give us any more details on exactly what you are doing? Perhaps there are better alternatives to this problem.

Progress bar is more then i can code today for some reason

I am having serious issues with a progress bar. I am building a custom backup utility, it allows users force their update now on a button click. When they click the button it calls a console application with the console window hidden runs the full backup process then completes. during the whole process there is no status or progress bar, due to the fact that after this install is done it will be transparent to the user....users cause issues we all know that. During the GUI interaction there needs to be something that tells the admin that it's doing something. here is my code:
private void forback_Click(object sender, EventArgs e)
{
BackgroundWorker bg = new BackgroundWorker();
bg.DoWork += new DoWorkEventHandler(MethodToGetInfo);
bg.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bg_RunWorkerCompleted);
progressBar1.Style = ProgressBarStyle.Marquee;
bg.RunWorkerAsync();
}
void MethodToGetInfo(Object sender, DoWorkEventArgs args)
{
Process info = new Process();
info.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
info.StartInfo.FileName = "C:\\Rameses\\Program\\Day_Cloud_Backup.exe";
info.StartInfo.UseShellExecute = false;
info.StartInfo.CreateNoWindow = true;
info.StartInfo.RedirectStandardOutput = true;
info.Start();
}
void bg_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs args)
{
MessageBox.Show("it's f***ing done");
}
what the heck am I doing wrong. I click the button, the app runs... but the message box shows right away and the progress bar doesn't stop.
Any Ideas?
Two things here.
For the Progress bar to stop, you need to tell it to stop in the RunWorkerCompleted callback (in your case, change the style).
For the thread to not return immediately, you need to block in the thread to wait until the Process you kicked off is completed. One way to do this is to use the WaitForExit Process method:
...
info.Start();
info.WaitForExit();
http://msdn.microsoft.com/en-us/library/fb4aw7b8.aspx
This will make the thread wait until your process is done before calling the WorkerCompleted callback.

Read from console process

I have a process, i can start, and hide working fine, but i want to read from the console program, when i runs, not after, i tried to run a timer, anbd read at the tick, but my program just crashes and when it not do, i get nothing at all.
startInfo= new ProcessStartInfo("cmd.exe");
startInfo.Arguments ="/C uus.exe "+ arg.ToString();
startInfo.RedirectStandardError = true;
startInfo.RedirectStandardOutput = true;
startInfo.UseShellExecute = false;
startInfo.CreateNoWindow = true;
this.timer1.Enabled=true;
this.listBox1.Items.Clear();
p= Process.Start(startInfo);
Application.DoEvents();
void Timer1Tick(object sender, EventArgs e)
{
string str="";
str=p.StandardOutput.ReadLine();
if(str != null)
{
this.Text=str.ToString();
this.listBox1.Items.Add(str);
}
Application.DoEvents();
}
So what do i do to solve this?
Update:
I tried bender suggestion
now My program don't crash anymore, but also don't recvie any data
proc.StartInfo.UseShellExecute=false;
proc.StartInfo.CreateNoWindow=true;
proc.StartInfo.RedirectStandardOutput=true;
proc.StartInfo.RedirectStandardError=true;
proc.StartInfo.FileName="uus.exe";
proc.StartInfo.Arguments=arg;
proc.OutputDataReceived += new System.Diagnostics.DataReceivedEventHandler(SortOutputHandler);
proc.Start();
proc.BeginOutputReadLine();
void SortOutputHandler(object o,System.Diagnostics.DataReceivedEventArgs e)
{
string str="";
string str2="";
str=e.Data.ToString();
if(str!=null && str!="")
{
this.listBox1.Items.Add(str.ToString());
this.Text=str.ToString();
}
str2=proc.StandardOutput.ReadLine();
if(str2!=null && str2!="")
{
this.lsw1.Items.Add(str2.ToString());
}
}
hmm?
Update:
I have changed the handler, because i have being tell, it can't do it, that it wil be cross thread operation, usualyy i wille have get an error if it was.
private delegate void TextAdderDelegate(string str);
void TextAdder(string str)
{
if(this.lsw1.InvokeRequired==true)
{
Invoke(new TextAdderDelegate(TextAdder),new object[] {str});
}
else
{
this.lsw1.Items.Add(str);
}
}
void SortOutputHandler(object o,System.Diagnostics.DataReceivedEventArgs e)
{
string str="";
if(e!=null)
{
if(e.Data!=null)
{
str=e.Data.ToString();
}
}
TextAdder(str);
}
The problem is that you're running on one thread and trying to write using another. When you created your background thread using the Timer's tick event, it can't have frontend user input.
Perhaps if you explained the big picture of what you're trying to accomplish, we can better help you.
In the meantime, you might want to create threadsafe writes. This article will help you to understand the problem and solution to writing to form controls on different threads.
You may create the Process instance explicitly (e.g. new Process)and use the OutputDataReceived event, the method BeginOutputReadLine() and, when finished CancelOutputRead() for that.
The event OutputDataReceived will be repeatedly called asynchronously from a different thread as soon output data is available.
I assume you get an 'thread cross exception', this may be caused because you're updating your form controls on an other thread then the UI thread.

Categories