简体   繁体   中英

Define timeout to specific line in C# WPF application while running command prompt .exe application

in my main WPF application code i need to run .exe app with command prompt. this action is executed inside backgroundworker i have the following code. the code is running readlines.exe app with command prompt and read the output lines into a string (str).

string str;
ProcessStartInfo proc = new ProcessStartInfo();
proc.WindowStyle = ProcessWindowStyle.Hidden;
proc.UseShellExecute = true;
proc.FileName = @"readlines.exe";
proc.Arguments = @"";
proc.UseShellExecute = false;
proc.RedirectStandardOutput = true;
proc.CreateNoWindow = true;
proc.RedirectStandardInput = true;

Process proc1 = Process.Start(proc);
proc1.StandardInput.WriteLine("");

str = proc1.StandardOutput.ReadToEnd();

i want to ad timeout to the below line so when the timeout will be finised the procces will be canceled (as CTR+C) and "str" will get the output text until this point.

str = proc1.StandardOutput.ReadToEnd();

is it possible?

Although the previous answer has already been accepted here is a maybe more useful, secure and performant solution. Further it does not make use of the ReadLine() method which would block until there has been one line written (which may never occur). It uses an instance of StringBuilder and reads from the stream in specifyable data blocks (default size is 128 characters). Furthermore it supports event based notification of read data.

The usage of the class stays the same.

ProcessOutputReader por = new ProcessOutputReader(proc1);
por.StartReading();

// Do whatever you want here
// (e.g. sleep or whatever)

por.StopReading();

// Now you have everything that has been read in por.Data

However I've added the OnDataRead event which is fired every time new data has been read. You can access the data by using eg following code:

...
// Subscribe to the event
por.OnDataRead += OnDataReadEventHandler;
...

The callback method / event handler would look something like this:

private void OnDataReadEventHandler(object sender, ProcessOutputReaderEventArgs e)
{
    // e.IntermediateDataStore points to the StringBuilder instance which holds
    // all the data that has been received until now.
    string completeData = e.IntermediateDataStore.ToString();

    // e.NewData points to a string which contains the data that has been received
    // since the last triggered event (because the event is triggered on each read).
    string newData = e.NewData;
}

The modified ProcessOutputReader class looks like this:

/// <summary>
/// Represents the ProcessOutputReader class.
/// </summary>
public class ProcessOutputReader
{
    /// <summary>
    /// Represents the instance of the thread arguments class.
    /// </summary>
    private ProcessOutputReaderWorkerThreadArguments threadArguments;

    /// <summary>
    /// Initializes a new instance of the <see cref="ProcessOutputReader"/> class.
    /// </summary>
    /// <param name="process">The process which's output shall be read.</param>
    /// <exception cref="System.ArgumentOutOfRangeException">Is thrown if the specified process reference is null.</exception>
    public ProcessOutputReader(Process process)
    {
        if (process == null)
        {
            throw new ArgumentOutOfRangeException("process", "The parameter \"process\" must not be null");
        }

        this.Process = process;
        this.IntermediateDataStore = new StringBuilder();
        this.threadArguments = new ProcessOutputReaderWorkerThreadArguments(this.Process, this.IntermediateDataStore);
    }

    /// <summary>
    /// Is fired whenever data has been read from the process output.
    /// </summary>
    public event EventHandler<ProcessOutputReaderEventArgs> OnDataRead;

    /// <summary>
    /// Gets or sets the worker thread.
    /// </summary>
    private Thread ReaderThread
    {
        get;
        set;
    }

    /// <summary>
    /// Gets or sets the intermediate data store.
    /// </summary>
    private StringBuilder IntermediateDataStore
    {
        get;
        set;
    }

    /// <summary>
    /// Gets the data collected from the process output.
    /// </summary>
    public string Data
    {
        get
        {
            return this.IntermediateDataStore.ToString();
        }
    }

    /// <summary>
    /// Gets the process.
    /// </summary>
    public Process Process
    {
        get;
        private set;
    }

    /// <summary>
    /// Stars reading from the process output.
    /// </summary>
    public void StartReading()
    {
        if (this.ReaderThread != null)
        {
            if (this.ReaderThread.IsAlive)
            {
                return;
            }
        }

        this.ReaderThread = new Thread(new ParameterizedThreadStart(ReaderWorker));
        this.threadArguments.Exit = false;
        this.ReaderThread.Start(this.threadArguments);
    }

    /// <summary>
    /// Stops reading from the process output.
    /// </summary>
    public void StopReading()
    {
        if (this.ReaderThread != null)
        {
            if (this.ReaderThread.IsAlive)
            {
                this.threadArguments.Exit = true;
                this.ReaderThread.Join();
            }
        }
    }

    /// <summary>
    /// Fires the OnDataRead event.
    /// </summary>
    /// <param name="newData">The new data that has been read.</param>
    protected void FireOnDataRead(string newData)
    {
        if (this.OnDataRead != null)
        {
            this.OnDataRead(this, new ProcessOutputReaderEventArgs(this.IntermediateDataStore, newData));
        }
    }

    /// <summary>
    /// Represents the worker method.
    /// </summary>
    /// <param name="data">The thread arguments, must be an instance of the <see cref="ProcessOutputReaderWorkerThreadArguments"/> class.</param>
    private void ReaderWorker(object data)
    {
        ProcessOutputReaderWorkerThreadArguments args;

        try
        {
            args = (ProcessOutputReaderWorkerThreadArguments)data;
        }
        catch
        {
            return;
        }

        try
        {
            char[] readBuffer = new char[args.ReadBufferSize];

            while (!args.Exit)
            {
                if (args.Process == null)
                {
                    return;
                }

                if (args.Process.HasExited)
                {
                    return;
                }

                if (args.Process.StandardOutput.EndOfStream)
                {
                    return;
                }

                int readBytes = this.Process.StandardOutput.Read(readBuffer, 0, readBuffer.Length);
                args.IntermediateDataStore.Append(readBuffer, 0, readBytes);

                this.FireOnDataRead(new String(readBuffer, 0, readBytes));
            }
        }
        catch (ThreadAbortException)
        {
            if (!args.Process.HasExited)
            {
                args.Process.Kill();
            }
        }
    }
}

In addition you need the ProcessOutputReaderWorkerThreadArguments class which looks like this:

/// <summary>
/// Represents the ProcessOutputReaderWorkerThreadArguments class.
/// </summary>
public class ProcessOutputReaderWorkerThreadArguments
{
    /// <summary>
    /// Represents the read buffer size,
    /// </summary>
    private int readBufferSize;

    /// <summary>
    /// Initializes a new instance of the <see cref="ProcessOutputReaderWorkerThreadArguments"/> class.
    /// </summary>
    /// <param name="process">The process.</param>
    /// <param name="intermediateDataStore">The intermediate data store.</param>
    public ProcessOutputReaderWorkerThreadArguments(Process process, StringBuilder intermediateDataStore)
    {
        this.ReadBufferSize = 128;
        this.Exit = false;
        this.Process = process;
        this.IntermediateDataStore = intermediateDataStore;
    }

    /// <summary>
    /// Gets or sets a value indicating whether the thread shall exit or not.
    /// </summary>
    public bool Exit
    {
        get;
        set;
    }

    /// <summary>
    /// Gets or sets the read buffer size in bytes.
    /// </summary>
    /// <exception cref="System.ArgumentOutOfRangeException">Is thrown if the specified value is not greather than 0.</exception>
    public int ReadBufferSize
    {
        get
        {
            return this.readBufferSize;
        }
        set
        {
            if (value <= 0)
            {
                throw new ArgumentOutOfRangeException("value", "The specified value for \"ReadBufferSize\" must be greater than 0.");
            }

            this.readBufferSize = value;
        }
    }

    /// <summary>
    /// Gets the process.
    /// </summary>
    public Process Process
    {
        get;
        private set;
    }

    /// <summary>
    /// Gets the intermediate data store.
    /// </summary>
    public StringBuilder IntermediateDataStore
    {
        get;
        private set;
    }
}

And the ProcessOutputReaderEventArgs class which looks like this:

/// <summary>
/// Represents the ProcessOutputReaderEventArgs class.
/// </summary>
public class ProcessOutputReaderEventArgs : EventArgs 
{
    /// <summary>
    /// Initializes a new instance of the <see cref="ProcessOutputReaderEventArgs"/> class.
    /// </summary>
    /// <param name="intermediateDataStore">The reference to the intermediate data store.</param>
    /// <param name="newData">The new data that has been read.</param>
    public ProcessOutputReaderEventArgs(StringBuilder intermediateDataStore, string newData)
    {
        this.IntermediateDataStore = intermediateDataStore;
        this.NewData = newData;
    }

    /// <summary>
    /// Gets the reference to the intermediate data store.
    /// </summary>
    public StringBuilder IntermediateDataStore
    {
        get;
        private set;
    }

    /// <summary>
    /// Gets the new data that has been read.
    /// </summary>
    public string NewData
    {
        get;
        private set;
    }
}

Some example how to achieve this ( attention, code not tested and can be improved )

ProcessOutputReader por = new ProcessOutputReader(proc1);
por.StartReading();

// Do whatever you want here
// (e.g. sleep or whatever)

por.StopReading();

// Now you have everything that has been read in por.Lines

The class would look like:

public class ProcessOutputReader
{
    public ProcessOutputReader(Process process)
    {
        this.Process = process;
        this.Lines = new List<string>();
    }

    public List<string> Lines
    {
        get;
        private set;
    }

    public Process Process
    {
        get;
        private set;
    }

    private Thread ReaderThread
    {
        get;
        set;
    }

    public void StartReading()
    {
        if (this.ReaderThread == null)
        {
            this.ReaderThread = new Thread(new ThreadStart(ReaderWorker));
        }

        if (!this.ReaderThread.IsAlive)
        {
            this.ReaderThread.Start();
        }
    }

    public void StopReading()
    {
        if (this.ReaderThread != null)
        {
            if (this.ReaderThread.IsAlive)
            {
                this.ReaderThread.Abort();
                this.ReaderThread.Join();
            }
        }
    }

    private void ReaderWorker()
    {
        try
        {
            while (!this.Process.HasExited)
            {
                string data = this.Process.StandardOutput.ReadLine();
                this.Lines.Add(data);
            }
        }
        catch (ThreadAbortException)
        {
            if (!this.Process.HasExited)
            {
                this.Process.Kill();
            }
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM