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

visual c#是否有可能为进程运行提供比进度条更多的反馈?

这不是其他答案中提到的最好的方法,但它仍然可以成功地工作。

可以打开一个文件进行读写,而不会阻塞其他文件的读写。只使用File.Open代替辅助方法,并提供额外的参数(FileModeFileShare)下面是一个完整的例子。请注意,一个线程保持文件打开以进行写入,第二个线程每次打开和关闭文件并读取所有行:

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