Console app async control

  • Thread starter Thread starter Alistair George
  • Start date Start date
A

Alistair George

Hi all.
A win gui has to run a console (cmd) app, desirably the cmd window is
hidden, which I will do by placing outside the view area.
The GUI must be able to parse the output from cmd and do various things
like show a progress meter etc. It should also be able to gracefully
close the cmd window if it hangs or user needs the thread to abort.
Would I be correct in assuming the cmd should be started in a thread
with async coms?
MethodInvoker with BeginInvoke?
Appreciate any pointers to articles on this subject (google search
reveals few).
Thank you.
 
If you look at ProcessStartInfo, you can suppress the visual console
(IIRC) with CreateNoWindow = true, and capture the output by setting
RedirectStandardOutput = false, UseShellExecute = false, and reading
from Process.StandardOutput once you have started the command.

When running a win-ui, the easiest option is to do the reading (from
StandardOutput) on a workder thread (not the UI thread), and then
(when you read something interesting) use yourForm.BeginInvoke() to
notify the UI. There is also an event-driven mechanism
(Process.OutputDataReceived), but this is trickier.

I'll put an example together if you like...

Marc
 
(exe)

using System;
using System.IO;

class Program {
static int Main(string[] args) {
try {
string root = args.Length == 1 ? args[0] :
Environment.CurrentDirectory;
string[] dirs = Directory.GetDirectories(args[0], "*.*",
SearchOption.AllDirectories);
int count = dirs.Length, lastPercent = -1;
for (int i = 0; i < count; i++) {
int percent = (i * 100) / count;
if (percent != lastPercent) {
Console.WriteLine(percent.ToString() + "%");
}
foreach (string file in Directory.GetFiles(dirs)) {
Console.WriteLine(file);
}
}
return 0;
} catch (Exception ex) {
Console.Error.WriteLine(ex.Message);
return -1;
}
}
}

(winform)

using System;
using System.Windows.Forms;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;

class SomeForm : Form {
static void Main() {
Application.EnableVisualStyles();
using (Form f = new SomeForm()) {
Application.Run(f);
}
}

Button goStop;
BackgroundWorker worker;
const string BUTTON_TEXT = "Go";
protected override void OnLoad(EventArgs e) {
base.OnLoad(e);
goStop = new Button();
goStop.Text = BUTTON_TEXT;
goStop.Click += new EventHandler(goStop_Click);
Controls.Add(goStop);
worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;
worker.WorkerReportsProgress = true;
worker.DoWork+=new DoWorkEventHandler(worker_DoWork);
worker.ProgressChanged += new
ProgressChangedEventHandler(worker_ProgressChanged);
worker.RunWorkerCompleted += new
RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
}

void worker_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e) {
goStop.Text = BUTTON_TEXT;
if (e.Cancelled) {
this.Text = "cancelled";
} else if ((int) e.Result == 0) {
this.Text = "complete";
} else {
this.Text = "error " + e.Result.ToString();
}
}

void worker_ProgressChanged(object sender,
ProgressChangedEventArgs e) {
goStop.Text = e.ProgressPercentage.ToString() + "%";
}

void goStop_Click(object sender, EventArgs e) {
if (worker.IsBusy) {
worker.CancelAsync();
} else {
worker.RunWorkerAsync();
}
}

void worker_DoWork(object sender, DoWorkEventArgs e)
{
string exe = @"c:\walker.exe", root = @"c:\develop\t";
ProcessStartInfo psi = new ProcessStartInfo(exe, root);
psi.UseShellExecute = false;
psi.RedirectStandardOutput = true;
psi.CreateNoWindow = true;
using(Process proc = Process.Start(psi))
using(StreamReader reader = proc.StandardOutput) {
worker.ReportProgress(0);
Regex re = new Regex(@"\d{1,3}%");
int index = 0;
while (!reader.EndOfStream) {
if (worker.CancellationPending) {
proc.Kill();
e.Cancel = true;
break;
}
string line = reader.ReadLine();
if (re.IsMatch(line)) {

worker.ReportProgress(int.Parse(line.TrimEnd('%')));
} else {
if (index++ % 20 == 0) {
this.Invoke((MethodInvoker)delegate {
this.Text = line;
});
}
}
}
if (!e.Cancel) {
e.Result = proc.ExitCode;
}

}
}
}
 
Marc said:
I'll put an example together if you like...

Marc
Thank you so much Marc. You have made the task very easy for me to
implement. On the few google results that I have read on the subject, it
is indicated that getting the output from the CMD window is unreliable
to say the least. This is a problem that the previous implimentation
had. So it will be interesting to see how well it works if you like I
will stay in touch.

Am trying to improve a situation whereby the previous method used to
occasionally hang when they used the cmd output. I am not sure whether
they used sync or async comms in that case.
Thanks again, Al.
 
I did some more playing with this, and it looks like (irritatingly) in
the event of a hang, the Begin... approach is more stable... sorry to
confuse things! I'll see if I can get that working...

Marc
 
Marc said:
I did some more playing with this, and it looks like (irritatingly) in
the event of a hang, the Begin... approach is more stable... sorry to
confuse things! I'll see if I can get that working...

Marc
OK, but are you saying that the method can hang due to comms problems?
 
OK, but are you saying that the method can hang due to comms problems?

Be careful when redirecting both standard output and standard error as
there can be a deadlock issue. Check the docs which give some more
details about this issue.

Chris
 
Chris said:
Be careful when redirecting both standard output and standard error as
there can be a deadlock issue. Check the docs which give some more
details about this issue.

Chris
Thanks Chris.
Mark did you want to go any further or shall I proceed with caution
using the code that you promulgated?
Cheers,
Alistair.
 
Back
Top