I'm just starting out look in to delegates and async callbacks, but having some difficulties understanding this. I have this form and dont want to wait for string x = SR.ReadToEnd(); to complete, I want to get feedback to the form with string x = _streamreader.ReadLine(); after each update in the cmd instance.
How should I go about doing this?
From my understanding I cold create a delegate that holds x? and returns this to main thread each time it gets updated?
using System;
using System.Windows.Forms;
using System.Diagnostics;
namespace WindowsFormsApplication2
{
public partial class Form1 : Form
{
private string _command = "tracert www.google.com";
private string _application = "cmd";
private string _exitCommand = "exit";
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
richTextBox1.Text += GetTrace();
}
private string GetTrace()
{
Process myprocess = new Process();
ProcessStartInfo StartInfo = new ProcessStartInfo();
StartInfo.FileName = _application;
StartInfo.RedirectStandardInput = true;
StartInfo.RedirectStandardOutput = true;
StartInfo.UseShellExecute = false;
StartInfo.CreateNoWindow = true;
myprocess.StartInfo = StartInfo;
myprocess.Start();
System.IO.StreamReader SR = myprocess.StandardOutput;
System.IO.StreamWriter SW = myprocess.StandardInput;
SW.WriteLine(_command);
SW.WriteLine(_exitCommand);
string x = SR.ReadToEnd();
SW.Close();
SR.Close();
return x;
}
}
}
UPDATE:
from the tip of Erno i created a object to hold the value, and updates in a while loop, then i read the object in the update event. Is this a ok method to do this? or is there a cleaner way of doing this?
using System;
using System.Windows.Forms;
using System.Diagnostics;
namespace WindowsFormsApplication2
{
public class StringHolder
{
public string Buffer { get; set; }
}
public partial class Form1 : Form
{
private string x;
private const string Command = "tracert www.google.com";
private const string Application = "cmd";
private const string ExitCommand = "exit";
readonly StringHolder sh = new StringHolder();
public Form1()
{
InitializeComponent();
}
private void button1_Click(object _sender, EventArgs _e)
{
GetTrace();
}
private void GetTrace()
{
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object _sender, System.ComponentModel.DoWorkEventArgs _e)
{
Process myprocess = new Process();
ProcessStartInfo StartInfo = new ProcessStartInfo();
StartInfo.FileName = Application;
StartInfo.RedirectStandardInput = true;
StartInfo.RedirectStandardOutput = true;
StartInfo.UseShellExecute = false;
StartInfo.CreateNoWindow = true;
myprocess.StartInfo = StartInfo;
myprocess.Start();
System.IO.StreamReader _streamreader = myprocess.StandardOutput;
System.IO.StreamWriter _streamwriter = myprocess.StandardInput;
_streamwriter.WriteLine(Command);
_streamwriter.WriteLine(ExitCommand);
while (_streamreader.EndOfStream == false)
{
lock(sh)
{
sh.Buffer = _streamreader.ReadLine();
}
backgroundWorker1.ReportProgress(1, null);
}
_streamwriter.Close();
_streamreader.Close();
}
private void backgroundWorker1_ProgressChanged(object _sender, System.ComponentModel.ProgressChangedEventArgs _e)
{
lock(sh)
{
if (sh.Buffer != x)
{
richTextBox1.Text += "\n" + sh.Buffer;
}
x = sh.Buffer;
}
}
}
}
It appears that the Buffer property is only used to report the progress. If that indeed is the case, I'd just pass in the line that was read as a second parameter in the call to ReportProgress. Then in the ProgressChanged handler, it could be accessed with the UserState property. This would get rid of the Buffer and the associated lock
..............
sh.Buffer = _streamreader.ReadLine();
.........
private void backgroundWorker1_ProgressChanged(object _sender, System.ComponentModel.ProgressChangedEventArgs _e)
{
richTextBox1.Text += "\n" + _e.UserState;
}
Did you consider the BackgroundWorker? Using its ReportProgress event will make this easy.
The ReportProgress event only allows the passing of an integer (progress) but you could fill an object with some information (the string?) and use the ReportProgress event to warn the main thread. Make sure you use appropriate locks when reading/writing the object.
Related
i'm trying to open a process with c# and react to it, when it's been closed. This work's for me:
private void StartProc()
{
var process = new System.Diagnostics.Process { StartInfo = { FileName = "PathTo.exe" } };
process.Start();
process.EnableRaisingEvents = true;
process.Exited += this.Editor_Exited;
}
private void Editor_Exited(object sender, EventArgs e)
{
MessageBox.Show("Process canceled");
}
Lets say I'm opening a text editor with this code. If there is already an instance of this text editor the code won't open a second instance and also jumps instant in the Editor_Exited Code.
I want the code to open a new instance and don't jump in the Editor_Exited code.
string processName = "PathTo.exe";
var process = new System.Diagnostics.Process { StartInfo = { FileName = processName } };
if (process.Start())
{
process.EnableRaisingEvents = true;
process.Exited += this.Editor_Exited;
}
else
{
var p = Process.GetProcessesByName(processName);
p.WaitForExit();
}
I get this is not 100% what you are asking for, but its a work around
i have write prog to get info about android devices
i using external process
create class apktool :
this code
namespace dream_tool
{
class apktool
{
public StringBuilder output = new StringBuilder();
public Process cmd = new Process();
public string RunExternalPing(string a)
{
cmd.StartInfo.FileName = "cmd.exe";
cmd.StartInfo.UseShellExecute = false;
cmd.StartInfo.CreateNoWindow = true;
cmd.StartInfo.RedirectStandardOutput = true;
cmd.StartInfo.RedirectStandardInput = true;
cmd.StartInfo.Arguments = a;
cmd.EnableRaisingEvents = true;
cmd.OutputDataReceived +=
new DataReceivedEventHandler(cmd_OutputDataReceived);
//cmd.Exited += new EventHandler(cmd_Exited);
cmd.Exited += new EventHandler(Form1.instance.cmd_Exited);
cmd.Start();
cmd.BeginOutputReadLine();
cmd.WaitForExit();
// StreamWriter sw = cmd.StandardInput;
//sw.WriteLine(a);
// cmd.WaitForExit();
// sw.Close();
return a;
}
private void cmd_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
Form1 frm = new Form1();
if (!String.IsNullOrEmpty(e.Data))
{
output.Append(e.Data + Environment.NewLine);
}
cmd.CancelOutputRead();
}
}
}
and in the form this code
public partial class Form1 : Form
{
public static Form1 instance;
apktool a2;
Procees_adb a1 = new Procees_adb();
List<String> fullFileName;
public Form1()
{
InitializeComponent();
instance = this;
a2 = new apktool();
}
public void cmd_Exited(object sender, EventArgs e)
{
if (RCH_OUT.InvokeRequired)
{
RCH_OUT.Invoke(new EventHandler(delegate { RCH_OUT.Text = (a2.output.ToString()); }));
}
else
{
RCH_OUT.Text = (a2.output.ToString());
MessageBox.Show(a2.output.ToString());
}
if(IsDisposed)
{
a2.cmd.Dispose();
}
//cmd.Dispose();
}
private void button2_Click(object sender, EventArgs e)
{
a2.RunExternalPing("/c adb shell getprop ro.product.cpu.abi");
}
}
[when i press on button in first time i get info and that ok
1
if i press it again
it show me error on cmd.BeginOutputReadLine();
System.InvalidOperationException: 'An async read operation has already been started on the stream.'
enter image description here
the first problem solve by add cmd.CancelOutputRead();
to
private void cmd_OutputDataReceived(object sender, DataReceivedEventArgs e)
but now when press button second time the info Repeated
Such as that in the picture
enter image description here
I'm developping an app in C# using microsoft visual studio (windows form).
What i want to do is to manage different environment through one GUI.
Thus, my gui have to start asynchronously some process (which are commandline applications).
The problem is that I can get the standard output of the process only once it's finished, meaning I can't show what the process is doing in runtime.
since the applications I want to run can take quite a long runtime (uploading big files ...) i would like to display the process output in runtime.
Thus, i created a backgroundworker to separate my gui from the process, and i tried to use a temporary file where the process output is written.
then using a FileSystemWatcher, I could use the "change" event to display the messages in my GUI.
My problem is that since the temporary file is open for writting, i can't read from it at the same time.
Here is my code, does anyone have a way to bypass this problem ? or an other way to do it ?
public partial class Form1 : Form
{
Boolean done = false;
private FileSystemWatcher observateur;
public Form1()
{
InitializeComponent();
// delete the temporary file if existing
if (System.IO.File.Exists("C:\\testoutput.txt"))
{
try
{
System.IO.File.Delete("C:\\testoutput.txt");
}
catch (System.IO.IOException exept)
{
Console.WriteLine(exept.Message);
return;
}
}
File.Create("C:\\testoutput.txt");
backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler
(backgroundWorker1_ProgressChanged);
observateur = new FileSystemWatcher();
observateur.Filter = "C:\\testoutput.txt";
observateur.Changed += new FileSystemEventHandler(this.OnChanged);
observateur.Created += new FileSystemEventHandler(this.OnCreate);
}
private void OnChanged(object source, FileSystemEventArgs e)
{
// I tried to bypass the problem of having the file opened by copying it but i doesn't work
File.Copy("C:\\testouput.txt", "C:\\TEMP.txt", true);
}
private void OnCreate(object source, FileSystemEventArgs e)
{
Console.WriteLine("Created");
}
private void button3_Click(object sender, EventArgs e)
{
string outputworker = "";
backgroundWorker1.RunWorkerAsync(outputworker);
while (!done)
{
string text = System.IO.File.ReadAllText("C:\\TEMP.txt");
Thread.Sleep(200);
}
}
void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
outputTextArea.Text = "Processing......" + progressBar1.Value.ToString() + "%";
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
string[] args = { "/k " };
string outputWork = e.Argument as string;
backgroundWorker1.ReportProgress(10);
System.Diagnostics.Process process = new System.Diagnostics.Process();
process.StartInfo.WorkingDirectory = "C:\\XXXXXXXXXX";
process.StartInfo.UseShellExecute = false;
process.StartInfo.FileName = "cmd.exe";
int nArgs = args.Length;
if (nArgs > 0)
{
process.StartInfo.Arguments = args[0];
}
for (int i = 1; i < nArgs; i++)
{
process.StartInfo.Arguments = String.Concat(process.StartInfo.Arguments, " && ", args[i]);
}
process.StartInfo.CreateNoWindow = true;
process.StartInfo.RedirectStandardInput = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
backgroundWorker1.ReportProgress(20);
process.Start();
backgroundWorker1.ReportProgress(40);
System.IO.StreamWriter sIn = process.StandardInput;
sIn.WriteLine("ExternalCommandLineApp1.exe >> C:\\testoutput.txt");
backgroundWorker1.ReportProgress(60);
sIn.WriteLine("ExternalCommandLineApp1.exe >> C:\\testoutput.txt");
System.IO.StreamReader sOut = process.StandardOutput;
backgroundWorker1.ReportProgress(90);
sIn.WriteLine("EXIT");
outputWork = sOut.ReadToEnd();
process.Close();
backgroundWorker1.ReportProgress(100);
e.Result = outputWork;
done = true;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
string output = e.Result as string;
//outputTextArea.Text = output;
}
}
This is not the best way as mentioned in other answers, but it still can work successfully.
You can open a file for reading/writing without blocking other reads/writes. Just use File.Open instead of helper methods and provide additional parameters (FileMode and FileShare)
Here is a complete example. Note that one thread keeps file opened for writing and second thread opens and closes file every time and reads all lines:
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string fileName = "c:\\_data\\temp.txt";
Task writer = new Task(() => {
using (FileStream fs = File.Open(fileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite))
using (StreamWriter sw = new StreamWriter(fs))
{
for (int i = 0; i < 50; i++ )
{
sw.WriteLine(DateTime.Now.Millisecond.ToString());
sw.Flush();
Thread.Sleep(500);
}
}
});
Task reader = new Task(() => {
for (int i = 0; i < 50; i++)
{
Thread.Sleep(500);
Console.WriteLine("Read again");
if (File.Exists(fileName))
{
using (FileStream fs = File.Open(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
using (StreamReader r = new StreamReader(fs))
{
while (!r.EndOfStream)
{
Console.WriteLine(r.ReadLine());
}
}
}
}
});
writer.Start();
reader.Start();
writer.Wait();
reader.Wait();
}
}
}
The simplest way with what you've already got is to exploit the UserState you can pass with the BackgroundWorker.
In the backgroundWorker1_DoWork method, you can use
backgroundWorker1.ReportProgress(0, "Whatever text you want to send right now.");
And in backgroundWorker1_ProgressChanged, you can read the message and put it in the text box like this:
outputTextArea.AppendText((e.UserState as string) + "\r\n");
This is a bit inefficient, but it should be much safer and faster than your original solution anyway.
In .NET, you've got many options of passing data between threads. If you want to learn more about the concepts, problems and solutions of multi-threading, you can give this a go: http://www.albahari.com/threading/
You can get the Standard Output of processes using System.Diagnostics.Process StandardOutput property (it's a Stream).
http://msdn.microsoft.com/en-us/library/vstudio/system.diagnostics.process.standardoutput(v=vs.90).aspx
I suggest you use Windows Communications Foundation to do this.
Following is a complete example.
There are two helper classes that you would normally put into a class library for reuse, class WcfServiceHost<T> and class WcfServiceProxy<T>.
This is a console app which you should run from the command line twice, passing a parameter of monitor for the first instance you start, and worker for the second instance.
Run it from the command like like this (assuming the app is called ConsoleApp1.exe):
start ConsoleApp1.exe monitor
start ConsoleApp1.exe worker
and then look at the output. The monitor instance is waiting for progress reports from the worker. The worker instance is reporting the progress, effectively by calling a function in the monitor instance (RPC, or Remote Procedure Call).
Here's the complete code. You will need to reference System.ServiceModel:
using System;
using System.Diagnostics;
using System.ServiceModel;
using System.Threading;
namespace Demo
{
[ServiceContract]
interface IProgressReporter
{
[OperationContract]
void ReportProgress(double percentComplete, string message);
}
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
sealed class Monitor: IProgressReporter
{
public void ReportProgress(double percentComplete, string message)
{
Console.WriteLine("Monitor received progress - Completed {0}%: {1}", percentComplete, message);
if (percentComplete == 100)
{
Program.ReportFinished();
}
}
}
public sealed class WcfServiceHost<T>: IDisposable where T: class
{
public WcfServiceHost(T service, string wcfEndpointAddress)
{
_service = service;
_wcfEndpointAddress = wcfEndpointAddress;
var serviceHost = new ServiceHost(service);
serviceHost.AddServiceEndpoint(typeof(T), new NetNamedPipeBinding(), wcfEndpointAddress);
serviceHost.Open();
_serviceHost = serviceHost;
}
public T Service
{
get
{
return _service;
}
}
public string WcfEndpointAddress
{
get
{
return _wcfEndpointAddress;
}
}
/// <summary>Disposal.</summary>
public void Dispose()
{
if (_serviceHost != null)
{
try
{
_serviceHost.Close();
}
catch (Exception exception) // Don't allow exceptions to escape from Dispose().
{
Trace.WriteLine("There was an exception while closing the host: " + exception.Message);
}
}
}
private readonly T _service;
private readonly string _wcfEndpointAddress;
private readonly ServiceHost _serviceHost;
}
public sealed class WcfServiceProxy<T>: IDisposable where T: class
{
public WcfServiceProxy(string wcfEndpointAddress)
{
_wcfEndpointAddress = wcfEndpointAddress;
_channelFactory = new ChannelFactory<T>(new NetNamedPipeBinding(), _wcfEndpointAddress);
_service = _channelFactory.CreateChannel();
_comms = _service as ICommunicationObject;
if (_comms == null)
{
throw new InvalidOperationException("proxy does not implement ICommunicationObject.");
}
}
public T Service
{
get
{
return _service;
}
}
public string WcfEndpointAddress
{
get
{
return _wcfEndpointAddress;
}
}
public void Dispose()
{
closeComms();
closeChannelFactory();
}
private void closeComms()
{
try
{
_comms.Close();
}
catch (CommunicationException exception) // Not closed - call Abort to transition to the closed state.
{
Debug.WriteLine("CommunicationException while closing ICommunicationObject: " + exception.Message);
_comms.Abort();
}
catch (TimeoutException exception) // Not closed - call Abort to transition to the closed state.
{
Debug.WriteLine("TimeoutException while closing ICommunicationObject: " + exception.Message);
_comms.Abort();
}
catch (Exception exception) // Not closed - call Abort to transition to the closed state.
{
Trace.WriteLine("Unexpected exception while closing ICommunicationObject: " + exception.Message);
_comms.Abort();
}
}
private void closeChannelFactory()
{
try
{
_channelFactory.Close();
}
catch (CommunicationException exception) // Not closed - call Abort to transition to the closed state.
{
Debug.WriteLine("CommunicationException while closing ChannelFactory: " + exception.Message);
_channelFactory.Abort();
}
catch (TimeoutException exception) // Not closed - call Abort to transition to the closed state.
{
Debug.WriteLine("TimeoutException while closing ChannelFactory: " + exception.Message);
_channelFactory.Abort();
}
catch (Exception exception) // Not closed - call Abort to transition to the closed state.
{
Trace.WriteLine("Unexpected exception while closing ChannelFactory: " + exception.Message);
_channelFactory.Abort();
}
}
private readonly T _service;
private readonly string _wcfEndpointAddress;
private readonly ChannelFactory<T> _channelFactory;
private readonly ICommunicationObject _comms;
}
internal static class Program
{
static void Main(string[] args)
{
if (args.Length > 0 && args[0] == "worker")
runWorker();
else
runMonitor();
Console.WriteLine("\nEnded. Press a key to exit.");
Console.ReadKey();
}
public static void ReportFinished()
{
finished.Set();
}
static void runMonitor()
{
using (new WcfServiceHost<IProgressReporter>(new Monitor(), SERVICE_PIPE_NAME))
{
finished.WaitOne();
}
}
static void runWorker()
{
using (var proxy = new WcfServiceProxy<IProgressReporter>(SERVICE_PIPE_NAME))
{
for (int i = 0; i <= 100; ++i)
{
Thread.Sleep(100);
Console.WriteLine("Worker reporting progress: Completed {0}%: {1}", i, i);
proxy.Service.ReportProgress(i, i.ToString());
}
}
}
private static ManualResetEvent finished = new ManualResetEvent(false);
private const string SERVICE_PIPE_NAME = "net.pipe://localhost/MyServicePipeName";
}
}
thanks to you i managed to do what i wanted ^^
Since it took me quite some time to search/debug, I share my solution.
I used a temporary text file, so it's not very "professional" but it works.
To run the process, you have to call :
string[] args = { "/c cmd1", "cmd2" , "cmd3"};
backgroundWorker1.RunWorkerAsync(args);
(sync on a button pressed event for example)
public partial class Form1 : Form
{
string fileName = "c:\\temp\\tempoutput.txt";
public Form1()
{
InitializeComponent();
backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler
(backgroundWorker1_ProgressChanged);
}
void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// This function fires on the UI thread so it's safe to edit the UI control directly
progressBar1.Value = e.ProgressPercentage;
readTempFile();
//outputTextArea.Text = "Processing......" + progressBar1.Value.ToString() + "%";
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
// command line
string[] args = e.Argument as string[];
backgroundWorker1.ReportProgress(2);
try
{
FileStream fs = File.Open(fileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite);
StreamWriter sw = new StreamWriter(fs);
sw.WriteLine("### Starting the process : ###");
sw.Flush();
System.Diagnostics.Process process = new System.Diagnostics.Process();
process.StartInfo.WorkingDirectory = "WorkdirPath";
process.StartInfo.UseShellExecute = false;
process.StartInfo.FileName = "cmd.exe";
// create the command line
int nArgs = args.Length;
if (nArgs > 0)
{
process.StartInfo.Arguments = args[0];
}
for (int i = 1; i < nArgs; i++)
{
process.StartInfo.Arguments = String.Concat(process.StartInfo.Arguments, " && ", args[i]);
}
process.StartInfo.CreateNoWindow = true;
process.StartInfo.RedirectStandardInput = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
backgroundWorker1.ReportProgress(5);
process.Start();
backgroundWorker1.ReportProgress(10);
System.IO.StreamWriter sIn = process.StandardInput;
System.IO.StreamReader sOut = process.StandardOutput;
backgroundWorker1.ReportProgress(15);
int timeCount = 15;
string tempOut = "";
while (!sOut.EndOfStream)
{
tempOut = sOut.ReadLine();
sw.WriteLine(tempOut);
sw.Flush();
if (timeCount < 90)
{
// increasing the progress bar value.
//timeCount += 1;
}
backgroundWorker1.ReportProgress(timeCount);
}
sw.WriteLine("Closing process");
sw.Flush();
process.Close();
backgroundWorker1.ReportProgress(100);
}
catch (System.IO.IOException exept)
{
Console.WriteLine(exept.Message);
return;
}
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
readTempFile();
}
private void readTempFile()
{
try
{
FileStream fs = File.Open(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
StreamReader r = new StreamReader(fs);
string output = r.ReadToEnd();
outputTextArea.Text = output;
}
catch (System.IO.IOException exept)
{
Console.WriteLine(exept.Message);
return;
}
}
}
I know little about C#.
I am trying to display a progressbar with the status of the background command line program.
After google examples of progressbar and run process in c#, so I'm trying to start a BackgroundWorker in the Windows Form, and run the command line program in the BackgroundWorker. I want to parse the command line's output to get the progress
percentage and display it to the progress bar.
The command line program is a console program, it will output periodically its current status of progress.
But it seems it did not work. I can't readline any lines of the process's standard output.
Is there anything I missed?
Here is the raw code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
using System.Drawing;
using System.Threading;
using System.Diagnostics;
using System.IO;
public class DemoForm : Form
{
private BackgroundWorker backgroundWorker1;
private Button loadButton;
private ProgressBar progressBar1;
public DemoForm()
{
InitializeComponent();
backgroundWorker1 = new System.ComponentModel.BackgroundWorker();
backgroundWorker1.DoWork += new System.ComponentModel.DoWorkEventHandler(
this.backgroundWorker1_DoWork);
backgroundWorker1.RunWorkerCompleted +=
new System.ComponentModel.RunWorkerCompletedEventHandler(
this.backgroundWorker1_RunWorkerCompleted);
backgroundWorker1.ProgressChanged +=
new System.ComponentModel.ProgressChangedEventHandler(
this.backgroundWorker1_ProgressChanged);
}
private void loadButton_Click(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
this.loadButton.Enabled = false;
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
string status;
Process p = new Process();
p.StartInfo.FileName = "xx.exe";
p.StartInfo.Arguments = "-q -r test.cap -p";
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.CreateNoWindow = true;
p.OutputDataReceived += this.p_OutputDataReceived;
p.EnableRaisingEvents = true;
p.Start();
p.BeginOutputReadLine();
/*
while((status = p.StandardOutput.ReadLine()) != null)
{
Console.WriteLine(status);
string[] words = status.Split(' ');
int offset = Convert.ToInt32(words[0]);
int size = Convert.ToInt32(words[1]);
int percentComplete = 100 * offset / size;
MessageBox.Show(words[0], words[1]);
backgroundWorker1.ReportProgress(percentComplete);
}
Console.WriteLine("wait for exit!");
StreamReader myStreamReader = p.StandardOutput;
status = myStreamReader.ReadLine();
Console.WriteLine(status);
Console.WriteLine("wait for exit!");
*/
p.WaitForExit();
}
void p_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
string status = e.Data;
Console.WriteLine("get events");
Console.WriteLine(status);
string[] words = status.Split(' ');
int offset = Convert.ToInt32(words[0]);
int size = Convert.ToInt32(words[1]);
int percentComplete = 100 * offset / size;
MessageBox.Show(words[0], words[1]);
backgroundWorker1.ReportProgress(percentComplete);
}
private void backgroundWorker1_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
progressBar1.Value = 100;
if (e.Error == null) {
MessageBox.Show ("Demo", "Loading completed");
}
}
private void backgroundWorker1_ProgressChanged(object sender,
ProgressChangedEventArgs e)
{
this.progressBar1.Value = e.ProgressPercentage;
}
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
private void InitializeComponent()
{
this.loadButton = new System.Windows.Forms.Button();
this.progressBar1 = new System.Windows.Forms.ProgressBar();
/* load button */
this.loadButton.Location = new System.Drawing.Point(12, 12);
this.loadButton.Name = "loadButton";
this.loadButton.Size = new System.Drawing.Size(100, 23);
this.loadButton.TabIndex = 0;
this.loadButton.Text = "Load file";
this.loadButton.UseVisualStyleBackColor = true;
this.loadButton.Click += new System.EventHandler(this.loadButton_Click);
/* progress bar */
this.progressBar1.Location = new System.Drawing.Point(12, 50);
this.progressBar1.Name = "progressBar1";
this.progressBar1.Size = new System.Drawing.Size(100, 26);
this.progressBar1.TabIndex = 1;
/* Form */
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(133, 104);
this.Controls.Add(this.progressBar1);
this.Controls.Add(this.loadButton);
this.Name = "DemoForm";
this.Text = "DemoForm";
this.ResumeLayout (false);
}
}
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.Run(new DemoForm());
}
}
Or is there any better way to get a command line program's progress?
You have to set the WorkerReportsProgress property of your backgroundWorker to true in order to be able to report while your backgroundWorker is running:
backgroundWorker1.WorkerReportsProgress = true;
After manually flush the output of the command line program, problem solved.
fflush(stdout);
It's not clear why it's buffered even a line ended.
Building on what Kamyar was stating, you would need to then code a delegate to capture the progress of your backgroundWorker object using a UserState object. This could be any object really, but you use it to update other aspects of the GUI, in this case a status label...
this.backgroundWorker1.WorkerReportsProgress = true;
this.backgroundWorker1.WorkerSupportsCancellation = true;
this.backgroundWorker1.DoWork += new System.ComponentModel.DoWorkEventHandler(this.backgroundWorker1_DoWork);
this.backgroundWorker1.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(this.backgroundWorker1_ProgressChanged);
this.backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted);
private class Progress
{
public string Status { get; set; }
public Progress(string status)
{
Status = status;
}
}
//...
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
while(myWork)
{
if(backgroundWorker1.CancellationPending)
{
e.Cancel = true;
break;
}
int p = //...percentage calc
backgroundWorker1.ReportProgress(p, new Progress("My message...")));
}
e.Cancel = false;
}
void backgroundWorker1_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
{
Progress p = (Progress)e.UserState;
lStatus.Text = p.Status;
progressBar.Value = e.ProgressPercentage;
}
This is just one method of implementing it though.
Thanks.
I have a form with 3 controls:
A textbox for the user to enter
commands to send to a console
application,
A button to confirm the commands to
be sent and
A read-only textbox to display the
output from the application.
What I want is for the user to enter commands in the first textbox, press the button to enter and receive feedback via the second textbox.
I know how to use ProcessStartInfo.RedirectStandardOutput but, however, the app hangs when I use StandardOutput.ReadToEnd().
I had a look at the asynchronous Process.BeginOutputReadLine() but, even though my app does not hang, somehow I get no response in the textbox, it does absolutely nothing.
Here's my code:
public partial class MainForm : Form
{
private void MainForm_Load(object sender, EventArgs e)
{
InitializeInterpreter();
}
private void InitializeInterpreter()
{
InterProc.StartInfo.UseShellExecute = false;
InterProc.StartInfo.FileName = "app.exe";
InterProc.StartInfo.RedirectStandardInput = true;
InterProc.StartInfo.RedirectStandardOutput = true;
InterProc.StartInfo.RedirectStandardError = true;
InterProc.StartInfo.CreateNoWindow = true;
InterProc.OutputDataReceived += new DataReceivedEventHandler(InterProcOutputHandler);
InterProc.Start();
}
private static void InterProcOutputHandler(object sendingProcess, DataReceivedEventArgs outLine)
{
if (!String.IsNullOrEmpty(outLine.Data))
{
OutputTextBox.Append(Environment.NewLine + outLine.Data);
}
}
private void Enterbutton_Click(object sender, EventArgs e)
{
InterProc.StandardInput.Write(CommandtextBox.Text);
InterProc.BeginOutputReadLine();
}
}
Is there any way I can have this run smoothly? Thanks.
If you want something interactive, I got this code to work (yours modified, details on modifications below)
private void InitializeInterpreter()
{
InterProc.StartInfo.UseShellExecute = false;
InterProc.StartInfo.FileName = "Echoer.exe";
InterProc.StartInfo.RedirectStandardInput = true;
InterProc.StartInfo.RedirectStandardOutput = true;
InterProc.StartInfo.RedirectStandardError = true;
InterProc.StartInfo.CreateNoWindow = true;
InterProc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
InterProc.OutputDataReceived += new DataReceivedEventHandler(InterProcOutputHandler);
bool started = InterProc.Start();
InterProc.BeginOutputReadLine();
}
private void AppendTextInBox(TextBox box, string text)
{
if (this.InvokeRequired)
{
this.Invoke((Action<TextBox, string>)AppendTextInBox, OutputTextBox, text);
}
else
{
box.Text += text;
}
}
private void InterProcOutputHandler(object sendingProcess, DataReceivedEventArgs outLine)
{
AppendTextInBox(OutputTextBox, outLine.Data + Environment.NewLine);
}
private void Enterbutton_Click(object sender, EventArgs e)
{
InterProc.StandardInput.WriteLine(CommandTextBox.Text);
}
So, I moved the BeginOutputReadLine to just after the process is started. That ensures it's really only called once. I also did an invoke required to clean up thread calls. Hopefully this should work for you.
The best solution I have found is:
private void Redirect(StreamReader input, TextBox output)
{
new Thread(a =>
{
var buffer = new char[1];
while (input.Read(buffer, 0, 1) > 0)
{
output.Dispatcher.Invoke(new Action(delegate
{
output.Text += new string(buffer);
}));
};
}).Start();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
process = new Process
{
StartInfo = new ProcessStartInfo
{
CreateNoWindow = true,
FileName = "app.exe",
RedirectStandardError = true,
RedirectStandardOutput = true,
UseShellExecute = false,
}
};
if (process.Start())
{
Redirect(process.StandardError, textBox1);
Redirect(process.StandardOutput, textBox1);
}
}
I've used code something like this:
public static void Run(string fileName, string arguments, out string standardOutput, out string standardError, out int exitCode)
{
Process fileProcess = new Process();
fileProcess.StartInfo = new ProcessStartInfo
{
FileName = fileName,
Arguments = arguments,
RedirectStandardError = true,
RedirectStandardOutput = true,
UseShellExecute = false,
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = true,
};
bool started = fileProcess.Start();
if (started)
{
fileProcess.WaitForExit();
}
else
{
throw new Exception("Couldn't start");
}
standardOutput = fileProcess.StandardOutput.ReadToEnd();
standardError = fileProcess.StandardError.ReadToEnd();
exitCode = fileProcess.ExitCode;
}
But it's not interactive. But if the app is interactive, it'll take a lot more code anyway.
Where are you calling StandardOutput.ReadToEnd()? I once had a similar problem because I was calling Process.WaitForExit() before StandardOutput.ReadToEnd(). I had a large amount of input, and the output buffer was full before completion and my process was blocked.
You must call StandardOutput.ReadToEnd()before Process.WaitForExit().