Timer callback doesn't work if new Process is started - c#

In the Form load I have this code which sets up a callback function to be called every 10 secs
private void Form1_Load(object sender, EventArgs e)
{
var timer = new System.Threading.Timer(_ => updateMethod(), null, 0,
10*1000);
}
In the updateMethod I start a new process to launch a legacy exe and read the output
private void updateMethod()
{
string console_exe = "MyLegacy.Exe";
Process prcss = new Process();
prcss .StartInfo.UseShellExecute = false;
prcss .StartInfo.RedirectStandardOutput = true;
prcss .StartInfo.FileName = console_exe;
prcss .StartInfo.Arguments = URL + " list";
prcss .Start();
prcss .WaitForExit();
string output = prcss.StandardOutput.ReadLine();
while (null != output)
{
output = prcss.StandardOutput.ReadLine();
}
}
updateMethod() is called exactly twice when I run my application.
After that it is just not called.
In the updateMethod() if I return before calling the new Process, the function gets called repeatedly until I close the application.
Don't understand why this method is called twice but not after that.
Is there another way of doing this instead of new Process() ?

Related

Redirected output from console application is VERY inconsistent

I've got a console application (steamcmd.exe) which I want to redirect its output to a textbox in my Winforms application.
https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip
I've managed to do that, but the output I get is very inconsistent. For example, I start steamcmd.exe normally with these arguments
+login anonymous +force_install_dir ./unturned/ +app_update 1110390 validate
This will install the Unturned game server. The output is perfectly fine. It shows the downloading progress consistently.
But when I start steamcmd.exe using my Winforms application and I redirect its output, it becomes super inconsistent, it could stay stuck at 2% and after a few minutes jump to 58%, the get stuck again and jump to 88% etc.
Here is my code:
steamCMDProc = new Process();
void startSteamCMD()
{
steamCMDProc.EnableRaisingEvents = true;
steamCMDProc.OutputDataReceived += new DataReceivedEventHandler(steamCMD_OutputDataReceived);
steamCMDProc.ErrorDataReceived += new DataReceivedEventHandler(steamCMD_ErrorDataReceived);
steamCMDProc.Exited += new System.EventHandler(steamCMDProcExited);
steamCMDProc.StartInfo.FileName = srvInstDir + #"\steamcmd.exe";
steamCMDProc.StartInfo.Arguments = "login anonymous +force_install_dir ./unturned/ +app_update 1110390 validate";
steamCMDProc.StartInfo.UseShellExecute = false;
steamCMDProc.StartInfo.RedirectStandardError = true;
steamCMDProc.StartInfo.RedirectStandardOutput = true;
steamCMDProc.StartInfo.RedirectStandardInput = true;
steamCMDProc.StartInfo.ErrorDialog = false;
steamCMDProc.StartInfo.CreateNoWindow = true;
steamCMDProc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
steamCMDProc.Start();
// inputWriter = steamCMDProc.StandardInput; not currently used
StartThread(steamCMDProcExited); //i start a thread which constantly loops and waits till steamCMD has exited
steamCMDProc.BeginErrorReadLine();
steamCMDProc.BeginOutputReadLine();
}
void steamCMD_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
InvokeIfRequired(textBox2, () => //function that checks if invoke is required
{ textBox2.AppendText(e.Data + Environment.NewLine); });
}
void steamCMD_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
InvokeIfRequired(textBox2, () =>
{ textBox2.AppendText(e.Data + Environment.NewLine); });
}

async interface for python script

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();

How is this code executing in a different thread?

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();
}

Events in BackgroundWorker thread not firing while main thread active

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.

ASP.NET/C# display output in runtime

I'm trying to start a process ("cmd.exe") and then launching another programs (from cmd.exe) and being able to get the output or send inputs in a textbox.
So i created a new Thread to not freeze the UI and then read the standard output and display it in the textbox.
But it seems that as soon as the process start, the link between my UI and the process is broken.
Here is my code :
public partial class exec2 : System.Web.UI.Page
{
public delegate void Worker();
private static Thread worker;
protected void Page_Load(object sender, EventArgs e)
{
}
public void setTextBox(string s)
{
TextBox1.Text = TextBox1.Text + s;
}
protected void RunEXE()
{
System.Diagnostics.Process proc = new System.Diagnostics.Process();
System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo();
psi.FileName = "cmd.exe";
psi.UseShellExecute = false;
psi.RedirectStandardOutput = true;
psi.RedirectStandardInput = true;
psi.RedirectStandardError = true;
psi.CreateNoWindow = true;
proc.StartInfo = psi;
setTextBox("Setting the process\n");
// Start the process
proc.Start();
setTextBox("Process started\n");
// Attach the output for reading
System.IO.StreamReader sOut = proc.StandardOutput;
// Attach the in for writing
System.IO.StreamWriter sIn = proc.StandardInput;
// Exit CMD.EXE
sIn.WriteLine("EXIT");
// Close the process
proc.Close();
setTextBox("Process closed");
string results = "";
while (!sOut.EndOfStream)
{
results = results + sOut.ReadLine().Trim() + "\n";
setTextBox(results.Replace(System.Environment.NewLine, "\n"));
}
// Close the io Streams;
sIn.Close();
sOut.Close();
}
protected void Button1_Click(object sender, EventArgs e)
{
Init(Work);
}
protected void Button2_Click(object sender, EventArgs e)
{
setTextBox("TEST\n");
}
public static void Init(Worker work)
{
worker = new Thread(new ThreadStart(work));
worker.Start();
}
public void Work()
{
RunEXE();
}
}
But only "setting the process" is displayed.
I think there is something I don't understand in the UI / process managment.
You are starting a worker thread and a process on the server; the UI render pipe doesn't sit there and wait for them - why would it? (your code just does new Thread(...).Start()). Frankly, you're lucky that you even see "Setting the process" - I would not expect that to keep working in the general case. An http response is disconnected; you won't see updates as other things happen. If you want to update a UI that has been sent to the client, you will need something like polling (ajax) or web-sockets.

Categories