visual c#是否有可能为进程运行提供比进度条更多的反馈?
本文关键字:有可能 是否 进程 运行 visual | 更新日期: 2023-09-27 18:16:40
我正在使用microsoft visual studio (windows窗体)开发c#应用程序。我想做的是通过一个GUI来管理不同的环境。
因此,我的gui必须异步启动一些进程(这是命令行应用程序)。问题是,只有在流程完成后,我才能获得流程的标准输出,这意味着我无法显示流程在运行时正在做什么。由于我想运行的应用程序可能需要相当长的运行时间(上传大文件…),我希望在运行时显示进程输出。
因此,我创建了一个后台工作者来将我的gui与进程分开,并且我试图使用一个临时文件来写入进程输出。然后使用FileSystemWatcher,我可以使用"change"事件在我的GUI中显示消息。我的问题是,因为临时文件是打开写,我不能从它读取在同一时间。这是我的代码,有没有人有办法绕过这个问题?或者有其他方法吗?
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;
}
}
这不是其他答案中提到的最好的方法,但它仍然可以成功地工作。
可以打开一个文件进行读写,而不会阻塞其他文件的读写。只使用File.Open
代替辅助方法,并提供额外的参数(FileMode
和FileShare
)下面是一个完整的例子。请注意,一个线程保持文件打开以进行写入,第二个线程每次打开和关闭文件并读取所有行:
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();
}
}
}
使用您已经获得的最简单的方法是利用您可以通过BackgroundWorker
传递的UserState
。
在backgroundWorker1_DoWork
方法中,您可以使用
backgroundWorker1.ReportProgress(0, "Whatever text you want to send right now.");
在backgroundWorker1_ProgressChanged
中,您可以读取消息并将其放入文本框中,如:
outputTextArea.AppendText((e.UserState as string) + "'r'n");
这是有点低效,但它应该比你原来的解决方案更安全,更快。
在。net中,你有很多在线程间传递数据的选项。如果你想了解更多关于多线程的概念、问题和解决方案,你可以试试:http://www.albahari.com/threading/
您可以使用System.Diagnostics.Process StandardOutput属性(它是一个流)获得进程的标准输出。
http://msdn.microsoft.com/en-us/library/vstudio/system.diagnostics.process.standardoutput (v =应用程序). aspx
我建议您使用Windows Communications Foundation。
下面是一个完整的例子。
您通常会将两个辅助类放入类库中以供重用,class WcfServiceHost<T>
和class WcfServiceProxy<T>
。
这是一个控制台应用程序,你应该从命令行运行两次,为第一个实例传递monitor
参数,为第二个实例传递worker
参数。
像这样从命令中运行它(假设应用程序名为ConsoleApp1.exe
):
start ConsoleApp1.exe monitor
start ConsoleApp1.exe worker
,然后查看输出。monitor
实例正在等待来自worker的进度报告。worker
实例通过调用monitor
实例中的函数(RPC,或远程过程调用)有效地报告进程。
下面是完整的代码。您需要引用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";
}
}
多亏了你,我才得以做我想做的事^^由于我花了相当多的时间来搜索/调试,我分享了我的解决方案。
我使用了一个临时文本文件,所以它不是很"专业",但它可以工作。
要运行该进程,必须调用:
string[] args = { "/c cmd1", "cmd2" , "cmd3"};
backgroundWorker1.RunWorkerAsync(args);
(同步按钮按下的事件为例)
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;
}
}
}