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.
Related
I have built a winform interface for my python program, my python program is a real time voice assistant, what I need is the interface should response instantly when the python gives outputs. I need to display standard output to the interface instantly. the below program is what I made.
in this code, the interface is not responding properly. python program executes in background continuously and not responding to the voice. i need a program that execute my python program and display the standard output to the winform interface.
namespace #interface
{
public partial class Form1 : Form
{
public static string text;
public Form1()
{
InitializeComponent();
}
private void pictureBox1_Click(object sender, EventArgs e)
{
}
private async void start_button_Click(object sender, EventArgs e)
{
string line;
int counter=0;
msg.Text = "Hey, Tell me something!";
Task task = new Task(Execute);
task.Start();
}
public void Execute()
{
// full path of python interpreter
string python = #"C:/Users/Jayasooryan/AppData/Local/Programs/Python/Python36-32/python.exe";
// python app to call
string myPythonApp = #"C:/Users/Jayasooryan/AppData/Local/Programs/Python/Python36-32/Avira.py";
// Create new process start info
ProcessStartInfo myProcessStartInfo = new ProcessStartInfo(python);
// make sure we can read the output from stdout
myProcessStartInfo.UseShellExecute = false;
myProcessStartInfo.RedirectStandardOutput = true;
myProcessStartInfo.CreateNoWindow = true;
// start python app with 3 arguments
// 1st arguments is pointer to itself,
// 2nd and 3rd are actual arguments we want to send
myProcessStartInfo.Arguments = myPythonApp;
Process myProcess = new Process();
// assign start information to the process
myProcess.StartInfo = myProcessStartInfo;
// start the process
myProcess.Start();
// Read the standard output of the app we called.
// in order to avoid deadlock we will read output first
// and then wait for process terminate:
StreamReader myStreamReader = myProcess.StandardOutput;
string myString = myStreamReader.ReadLine();
text = myString;
//Console.WriteLine(myString);
/*if you need to read multiple lines, you might use:
string myString = myStreamReader.ReadToEnd() */
// wait exit signal from the app we called and then close it.
myProcess.WaitForExit();
myProcess.Close();
// write the output we got from python app
//Console.ReadLine();
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
}
}
}
First: you should not try to read a single line from the python process, but rather use the OutputDataReceived event to be triggered when the process writes new data.
Second: since the output is buffered, you probably want to flush it after writing in your python process.
So here's a simple python script that keeps writing to the standard output (note how stdout.flush is called):
import random
import time
import sys
while True:
rand = random.randint(1, 10)
time.sleep(1)
print rand
sys.stdout.flush()
if rand in (9, 10):
break
And here's a simple Form that reads the output of that very script:
var f = new Form();
var t = new TextBox();
t.Dock = DockStyle.Fill;
t.Multiline = true;
f.Controls.Add(t);
f.Load += (s, e) => {
Process process = new Process();
process.StartInfo.FileName = "python";
process.StartInfo.Arguments = #"d:\script.py";
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.OutputDataReceived += (s2, e2) => {
t.Text += e2.Data + Environment.NewLine;
};
process.Start();
process.BeginOutputReadLine();
};
f.ShowDialog();
I'm trying to run a command in the command prompt and run it as a service. This command starts the queue listener from Laravel. I want to run it as a service so this queue listener always runs in the background. When the listener outputs some lines I want to capture these and send an email. I already tried my code running it as a process and it's working, but when I try to run the code as a service it doesn't start.
Process process();
protected override void OnStart(string[] args)
{
process = new Process();
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.Arguments = "/C php artisan queue:listen --tries=3 --timeout=0 --memory=1024";
process.StartInfo.FileName = "cmd.exe";
process.StartInfo.WorkingDirectory = "C:/xampp/htdocs/phpproject";
process.OutputDataReceived += new DataReceivedEventHandler(p_OutputDataReceived);
process.Start();
process.BeginOutputReadLine();
process.WaitForExit();
}
private void p_OutputDataReceived(object sendingProcess,
DataReceivedEventArgs outLine)
{
// Collect the command output.
if (!String.IsNullOrEmpty(outLine.Data.ToString()))
{
sendMail(process.StandardOutput.ToString());
}
}
Edit:
When I comment the process.WaitForExit() line the service runs and the queue listener does its work!.
But the next problem I have is the service never hits the eventhandler. It did when I was running it only as a process. Any clue why this isn't working as a service?
You can try something like this;
private Process process = null;
private DataReceivedEventHandler TheDataReceievedEventHandler;
private void startProcess()
{
ProcessStartInfo processStartInfo = new ProcessStartInfo(#"cmd.exe", #"/C php artisan queue:listen --tries=3 --timeout=0 --memory=1024")
{
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
WorkingDirectory = #"C:/xampp/htdocs/phpproject",
};
if ((process = Process.Start(processStartInfo)) != null)
{
process.EnableRaisingEvents = true;
process.Exited += new EventHandler(ExitedHandler);
TheDataReceievedEventHandler = new DataReceivedEventHandler(StandardOutputHandler);
process.OutputDataReceived += TheDataReceievedEventHandler;
process.BeginOutputReadLine();
process.ErrorDataReceived += TheDataReceievedEventHandler;
process.BeginErrorReadLine();
}
}
private void ExitedHandler(object sender, EventArgs e)
{
throw new NotImplementedException(); // the service you're trying to run closed it self.
}
private void StandardOutputHandler(object sender, DataReceivedEventArgs e)
{
Console.WriteLine(e.Data);
}
The problem is if the service you are trying to run closes it self, it will stop outputting and the process will be closed. Similar to CMD behavior each time you send a command to CMD it will close it self after you receive the error or output.
So if for example; I want to use CMD to see my task-list, i will have to build a loop were i run a CMD process on a regular interval because after each command it will close it self.
Edit
If you can't stop the program using its own logic you will need to kill the process of the program itself. You can do that using the following code;
try // If you have no administrator privilege, try will fire.
{
foreach (Process proc in Process.GetProcessesByName("name process")) // You can get the name by looking in your task manager.
{
proc.Kill();
}
}
catch(Exception ex)
{
// Add error handling
}
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;
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)