Below is my code so far, I am having an issue when I call Dispatcher.BeginInvoke, it does not process these messages at the correct time
Class Script:
public void Execute()
{
var process = new Process();
var startinfo = new ProcessStartInfo("cmd.exe", #"/C c:\test\my.bat");
startinfo.WorkingDirectory = "c:\\test";
startinfo.RedirectStandardOutput = true;
startinfo.RedirectStandardError = true;
startinfo.UseShellExecute = false;
startinfo.CreateNoWindow = true;
process.EnableRaisingEvents = true;
process.StartInfo = startinfo;
process.OutputDataReceived += (sender, args) => OutputDataReceived(args.Data);
process.ErrorDataReceived += (sender, args) => ErrorDataReceived(args.Data);
process.Exited += Exited;
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
int exitCode = process.ExitCode;
}
public void OutputDataReceived(string data)
{
Logging.Logger.Log("data received in script - " + data);
// throw event if we have a subscriber, else just return
if (OnScriptOutPut == null) return;
allFormattedOutPut += Environment.NewLine;
allFormattedOutPut += data;
allRawOutPut += data;
ScriptOutputEventArgs args = new ScriptOutputEventArgs(data);
OnScriptOutPut(this, args);
}
WPF Window calls the script class and subscribes to OnScriptOutPut event
The problem is below, UpdateOutPutTextBox only gets called after the script is finished, then all the updateoutputtextbox messages are processed all at once, they do not get processed when the begininvoke is called causing the screen to get updated at the end instead of when new output data is received.. Any help is appreciated!
private void btnRunScript_Click(object sender, RoutedEventArgs e)
{
Script script = new Script();
script.OnScriptOutPut += script_OnScriptOutPut;
script.Execute();
}
private void script_OnScriptOutPut(object sender, ScriptOutputEventArgs args)
{
Application.Current.Dispatcher.BeginInvoke(new Action(() => UpdateOutPutTextBox(args.Data)),System.Windows.Threading.DispatcherPriority.Send);
Logging.Logger.Log("data received in event ");
}
private void UpdateOutPutTextBox(string data)
{
Logging.Logger.Log("data received in update "+data);
tbOutput.Text += Environment.NewLine;
tbOutput.Text += data;
}
You are calling Execute on the UI thread and blocking the thread with WaitForExit. Then all of the BeginInvoke actions are getting queued up. Remove the call to WaitForExit. If you need to do something with the exit code, get the value in the Exited event handler.
I can not go through the whole code out there,
But looking into your query,
Dispatcher.BeginInvoke
BeginInvoke -> is like calling async , and async operations may take time depending on the conditions, use Invoke instead if you can, your code is alot ! reduse it if possible!
Related
This question already has answers here:
How to spawn a process and capture its STDOUT in .NET? [duplicate]
(9 answers)
Closed 5 years ago.
In my form, I start things by running an executable file loader.exe(another project in visual studio) which prints some information on console over time until it terminates. What I want to do is:
I want to read console while it continues executing and display the latest output(percentage with some extra info) in a text box textBoxConsole1 in my form application so that the user can be aware of the progress.
EDIT: Currently, it is a bit complicated. It displays some of the output, then displays additional output and then displays the whole remaining output. Not as same as the loader.exe does.
In this thread
C# Show output of Process in real time
Mr.Passant said:
"This is pretty normal, the process will switch to buffered output when you redirect its output. If it doesn't spit out a lot a text then that buffer doesn't fill up enough to cause it to be flushed. Nothing you can do about it if you can't fix the program's code."
So how much is this "enough" exactly? My Code:
private void buttonConnect_Click(object sender, EventArgs e)
{
Thread findThread = new Thread(findProcedure);
findThread.Start();
}
public void findProcedure()
{
Process process = new Process();
process.StartInfo.FileName = PATH;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardInput = true;
process.StartInfo.CreateNoWindow = true;
process.OutputDataReceived += new DataReceivedEventHandler((sender, e) =>
{
if (!String.IsNullOrEmpty(e.Data))
{
//textBoxConsole1.Text = e.Data; //Cross-thread validation exception
//use thread safe set method instead
setConsole1(e.Data);
}
});
process.ErrorDataReceived += new DataReceivedEventHandler((sender, e) =>
{
if (!String.IsNullOrEmpty(e.Data))
{
setConsole3(e.Data);
}
});
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
}
And my thread-safe set method:
public void setConsole1(string str)
{
if (this.textBoxConsole1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(setConsole1);
this.Invoke(d, new object[] { str });
}
else
{
textBoxConsole1.AppendText(str);
textBoxConsole1.AppendText(Environment.NewLine);
}
}
Error data handler method setConsole3 is the same as setConsole1 but sets to another box.
You should set RedirectStandardOutput to true in StartInfo.
Process process = new Process();
try
{
process.StartInfo.FileName = fileName // Loader.exe in this case;
...
//other startInfo props
...
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardOutput = true;
process.OutputDataReceived += OutputReceivedHandler //OR (sender, e) => Console.WriteLine(e.Data);
process.ErrorDataReceived += ErrorReceivedHandler;
process.Start();
process.BeginOutputReadline();
process.BeginErrorReadLine();
....
//other thing such as wait for exit
}
I'm trying to execute some Python scripts from my WPF app. The scripts are generating the log files and the code in the Tick event is reading them and displaying that as it is in a textbox.
My issue here is that, that LaunchProcess fires successfully, but the UI freezes. I have an indefinite progress bar, which too does not start animating. I'm a beginner with WPF and there is something very small I have to do to get this code working. I'm not getting any error/warnings. The scripts run fine and in the end I get to know the result too. But during the run, the UI of my app freezes.
private void LaunchProcess(string paramStr)
{
Process myProcess = new Process();
StartProgressBar();
try
{
dispatcherTimer = new DispatcherTimer();
dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
dispatcherTimer.Interval = new TimeSpan(0, 0, 0);
dispatcherTimer.Start();
myProcess.StartInfo.UseShellExecute = false;
// You can start any process
myProcess.StartInfo.FileName = "C:\\Python32\\python.exe";
myProcess.StartInfo.Arguments = "\""+paramStr+"\"";
myProcess.StartInfo.CreateNoWindow = true;
myProcess.StartInfo.RedirectStandardOutput = true;
myProcess.StartInfo.RedirectStandardError = true;
myProcess.Start();
myProcess.WaitForExit();
// This code assumes the process you are starting will terminate itself.
// Given that is is started without a window so you cannot terminate it
// on the desktop, it must terminate itself or you can do it programmatically
// from this application using the Kill method.
dispatcherTimer.Stop();
}
catch
{
MessageBox.Show("Process Launch Failed!!", "Failure", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void dispatcherTimer_Tick(object sender, EventArgs e)
{
//txtOutPut.Text = "";
txtOutPut.Text += "\n" + DateTime.Now.ToString();
if (File.Exists(scriptPath+"\\log.txt"))
{
//File.Copy("C:\\FlashAuto\\Execution_Logs\\log.txt", "C:\\FlashAuto\\Temp\\log.txt", true);
TextReader readLogs = new StreamReader(scriptPath + "\\log.txt");
string line = readLogs.ReadLine();
while (line != null)
{
txtOutPut.Text += "\n" + line;
line = readLogs.ReadLine();
txtOutPut.ScrollToEnd();
}
//CountLines = txtExecLog.LineCount - 1;
readLogs.Close();
// Forcing the CommandManager to raise the RequerySuggested event
txtOutPut.ScrollToEnd();
CommandManager.InvalidateRequerySuggested();
readLogs.Dispose();
}
else
{
txtOutPut.Text += "log file not found at: " + DateTime.Now.ToString();
}
}
In case you call LaunchProcess from the UI thread it will obviously be blocked at myProcess.WaitForExit().
You might simply remove the myProcess.WaitForExit() and dispatcherTimer.Stop() calls from the launch method and check if the process is still running in the timer Tick handler.
private void dispatcherTimer_Tick(object sender, EventArgs e)
{
if (myProcess.WaitForExit(0)) // check with timeout zero
{
dispatcherTimer.Stop();
}
... // all your code
}
Calling LaunchProcess method asynchronously would resolve your UI Freeze Issue
public void LaunchProcessAsynchrousCall(string paramStr)
{
ThreadStart displayContentHandler = delegate()
{
LaunchProcess(paramStr)
};
Thread thread = new Thread(displayContentHandler);
thread.IsBackground = true;
thread.Start();
}
I have implemented the following code (adapted from the tutorial) in order to run a command prompt window, run a program and read the output. The code is called from a ButtonClick event handler, which is nested in a User Control.
I was under the impression that this would allow the rest of my program to function whilst the external process runs, due to the fact that the methods are 'asynchronous'. However, this does not appear to be the case, as my UI will freeze while the operation is running. I should add that the output received when the cmd process ends is correct.
Sorry to dump a load of code like this, just not sure what else to do at this point!
Any assistance would be greatly appreciated.
public static void runExternalProcess()
{
StringBuilder output = new StringBuilder();
Process cmd = new Process();
cmd.StartInfo.FileName = "cmd.exe";
cmd.StartInfo.UseShellExecute = false;
cmd.StartInfo.CreateNoWindow = true;
cmd.StartInfo.RedirectStandardOutput = true;
cmd.OutputDataReceived += new DataReceivedEventHandler(outputEventHandler);
cmd.StartInfo.RedirectStandardInput = true;
cmd.Start();
cmd.BeginOutputReadLine();
StreamWriter sortStreamWriter = cmd.StandardInput;
StreamWriter sw = cmd.StandardInput;
if (sw.BaseStream.CanWrite)
{
sw.WriteLine("ping www.google.com");
}
sw.Close();
cmd.WaitForExit();
MessageBox.Show(output.ToString());
cmd.Close();
}
private static void outputEventHandler(object sendingProcess, DataReceivedEventArgs e)
{
if (!String.IsNullOrEmpty(e.Data))
{
output.Append(e.Data + Environment.NewLine);
}
}
How about registering for the Exited event and showing the MessageBox there:
StringBuilder output = new StringBuilder();
Process cmd = new Process();
public void RunExternalPing()
{
cmd.StartInfo.FileName = "cmd.exe";
cmd.StartInfo.UseShellExecute = false;
cmd.StartInfo.CreateNoWindow = true;
cmd.StartInfo.RedirectStandardOutput = true;
cmd.StartInfo.RedirectStandardInput = true;
cmd.EnableRaisingEvents = true;
cmd.OutputDataReceived +=
new DataReceivedEventHandler(cmd_OutputDataReceived);
cmd.Exited += new EventHandler(cmd_Exited);
cmd.Start();
cmd.BeginOutputReadLine();
StreamWriter sw = cmd.StandardInput;
sw.WriteLine("ping www.google.com");
sw.Close();
}
void cmd_Exited(object sender, EventArgs e)
{
MessageBox.Show(output.ToString());
cmd.Dispose();
}
private void cmd_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
if (!String.IsNullOrEmpty(e.Data))
{
output.Append(e.Data + Environment.NewLine);
}
}
From MSDN:
There are two ways of being notified when the associated process
exits: synchronously and asynchronously. Synchronous notification
relies on calling the WaitForExit method to pause the processing of
your application until the associated component exits. Asynchronous
notification relies on the Exited event. In either case,
EnableRaisingEvents must be set to true for the Process component to
receive notification that the process has exited.
Your problem is here:
cmd.WaitForExit();
This is a blocking call.
If you want to respond to the process exiting without blocking then you need to add a handler for the Exited event.
All this code is linear, if you don't want to freeze the thread you're in, you should create a new thread and perform a callback when that thread is finished.
Check out BackgroundWorker.
i have a method that creates a Process calling a console app.
double myProcess()
{
double results;
Process process = new Process();
process.EnableRaisingEvents = true;
process.OutputDataReceived += new DataReceivedEventHandler(process_OutputDataReceived);
process.StartInfo.FileName = filename;
process.StartInfo.Arguments = argument;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.CreateNoWindow = true;
process.Start();
process.BeginOutputReadLine();
process.WaitForExit();
return results;
}
static void process_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
string stringResults = (string)e.Data;
.
.
do some work on stringResults...
.
.
results = stringResults;
}
my problem is,how do i send data from process_OutputDataReceived back to myProcess? I cannot use singleton as there are possibilities that this process will be executed in multiple thread.
You don't need a separate method for the OutputDataReceived handler; you can use an anonymous method to set the results variable directly:
process.OutputDataReceived += (sender, e) => results = e.Data;
(Also, should results be string or double?)
Edit: A couple of alternatives for when you need to do more work in the handler:
process.OutputDataReceived +=
(sender, e) =>
{
string stringResults = e.Data;
// do some work on stringResults...
results = stringResults;
}
// or
process.OutputDataReceived +=
delegate(object sender, DataReceivedEventArgs e)
{
string stringResults = e.Data;
// do some work on stringResults...
results = stringResults;
}
For instance, a thread that is a BackgroundWorker, can be cast like:
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
System.ComponentModel.BackgroundWorker senderWorker
= sender as System.ComponentModel.BackgroundWorker;
}
The code above represents what I have for my Background worker thread. I cast [sender] as a BackGround Worker - because I know thats what he is.
I can't seem to find what I should cast it to if: instead of a Background worker, what if I had used a Process class, and executed say a DOS batch file, using:
enter code here
Process proc = new Process();
proc.FileName = "some_dos_batch_file.bat";
proc.Exited = ProcessExited;
proc.Start();
Sorry about syntax, but when this process completes, its completion will be handled by 'ProcessExited' below. But What should I cast the sender arg to in THAT case - NOT a Background Worker obviously, but I'm not sure to what? I would like to use the .Results property the same as I did for the Background worker.
Thanks - sorry for the confusion.
enter code here
void ProcessExited(object sender, EventArgs e)
{
}
I hope I have understood your question, if not, please clarify. If you are talking about threading and using the System.Diagnostics.Process then you would need to use Thread events...consider this below a simple class called TestARP that shells out to the command line using a hidden window to retrieve the MAC/IP address of the active connection, with the output of the command redirected to a stream which is appended to a stringbuilder instance:
public class TestARP
{
private StringBuilder sbRedirectedOutput = new StringBuilder();
public string OutputData
{
get { return this.sbRedirectedOutput.ToString(); }
}
public void Run()
{
System.Diagnostics.ProcessStartInfo ps = new System.Diagnostics.ProcessStartInfo();
ps.FileName = "arp";
ps.ErrorDialog = false;
ps.Arguments = "-a";
ps.CreateNoWindow = true;
ps.UseShellExecute = false;
ps.RedirectStandardOutput = true;
ps.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
using (System.Diagnostics.Process proc = new System.Diagnostics.Process())
{
proc.StartInfo = ps;
proc.Exited += new EventHandler(proc_Exited);
proc.OutputDataReceived += new System.Diagnostics.DataReceivedEventHandler(proc_OutputDataReceived);
proc.Start();
proc.WaitForExit();
proc.BeginOutputReadLine();
while (!proc.HasExited) ;
}
}
void proc_Exited(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine("proc_Exited: Process Ended");
}
void proc_OutputDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e)
{
if (e.Data != null) this.sbRedirectedOutput.Append(e.Data + Environment.NewLine);
//System.Diagnostics.Debug.WriteLine("proc_OutputDataReceived: Data: " + e.Data);
}
}
If you were to run this in a thread the Process's events will still get caught (only on the thread itself), but if you're talking about waiting for the thread to finish, look at this class code here called ThreadTestARP that runs the above class on a thread...
public class ThreadTestARP
{
private TestARP _testARP = new TestARP();
private ManualResetEvent _mre = new ManualResetEvent(false);
public ThreadTestARP()
{
}
public TestARP ARPTest
{
get { return this._testARP; }
}
public void Run()
{
Thread t = new Thread(new ThreadStart(RunThread));
t.Start();
this._mre.WaitOne();
// Blocks here...
t.Join();
}
private void RunThread()
{
this._testARP.Run();
this._mre.Set();
}
}
Note how the ManualResetEvent _mre is used to signal to say in the context of the thread, "right, I am done, back to the creator..."
Why can't you cast to a Process object? You can still access some members of Process objects, such as ExitCode or ExitTime, that have terminated.
http://msdn.microsoft.com/en-us/library/system.diagnostics.process.exited.aspx