Пример #1
0
 /// <summary>
 /// Runs an external process asynchronously, providing callbacks to
 /// capture binary data from the standard error and standard output streams.
 /// The callbacks contain a reference to the process so you can respond to output or
 /// error streams by writing to the process' input stream.
 /// The exit code (return value) will be -1 for forceful termination of the process.
 /// </summary>
 /// <param name="filename">The filename.</param>
 /// <param name="arguments">The arguments.</param>
 /// <param name="onOutputData">The on output data.</param>
 /// <param name="onErrorData">The on error data.</param>
 /// <param name="syncEvents">if set to <c>true</c> the next data callback will wait until the current one completes.</param>
 /// <param name="cancellationToken">The cancellation token.</param>
 /// <returns>Value type will be -1 for forceful termination of the process.</returns>
 /// <example>
 /// The following example illustrates how to run an external process using the
 /// <see cref="RunProcessAsync(string, string, ProcessDataReceivedCallback, ProcessDataReceivedCallback, bool, CancellationToken)"/>
 /// method.
 /// <code>
 /// class Example
 /// {
 ///     using System.Diagnostics;
 ///     using System.Text;
 ///     using System.Threading.Tasks;
 ///     using Swan;
 ///
 ///     static async Task Main()
 ///     {
 ///         // Execute a process asynchronously
 ///         var data = await ProcessRunner
 ///         .RunProcessAsync("dotnet", "--help", Print, Print);
 ///
 ///         // flush all messages
 ///         Terminal.Flush();
 ///     }
 ///
 ///     // a callback to print both output or errors
 ///     static void Print(byte[] data, Process proc) =>
 ///         Encoding.GetEncoding(0).GetString(data).WriteLine();
 /// }
 /// </code>
 /// </example>
 public static Task <int> RunProcessAsync(
     string filename,
     string arguments,
     ProcessDataReceivedCallback onOutputData,
     ProcessDataReceivedCallback onErrorData,
     bool syncEvents = true,
     CancellationToken cancellationToken = default)
 => RunProcessAsync(
     filename,
     arguments,
     null,
     onOutputData,
     onErrorData,
     Definitions.CurrentAnsiEncoding,
     syncEvents,
     cancellationToken);
Пример #2
0
        /// <summary>
        /// Copies the stream asynchronously.
        /// </summary>
        /// <param name="process">The process.</param>
        /// <param name="baseStream">The source stream.</param>
        /// <param name="onDataCallback">The on data callback.</param>
        /// <param name="syncEvents">if set to <c>true</c> [synchronize events].</param>
        /// <param name="ct">The cancellation token.</param>
        /// <returns>Total copies stream.</returns>
        private static Task <ulong> CopyStreamAsync(
            Process process,
            Stream baseStream,
            ProcessDataReceivedCallback onDataCallback,
            bool syncEvents,
            CancellationToken ct)
        {
            return(Task.Factory.StartNew(async() =>
            {
                // define some state variables
                var swapBuffer = new byte[2048]; // the buffer to copy data from one stream to the next
                ulong totalCount = 0;            // the total amount of bytes read
                var hasExited = false;

                while (ct.IsCancellationRequested == false)
                {
                    try
                    {
                        // Check if process is no longer valid
                        // if this condition holds, simply read the last bits of data available.
                        int readCount; // the bytes read in any given event
                        if (process.HasExited || process.WaitForExit(1))
                        {
                            while (true)
                            {
                                try
                                {
                                    readCount = await baseStream.ReadAsync(swapBuffer, 0, swapBuffer.Length, ct);

                                    if (readCount > 0)
                                    {
                                        totalCount += (ulong)readCount;
                                        onDataCallback?.Invoke(swapBuffer.Skip(0).Take(readCount).ToArray(), process);
                                    }
                                    else
                                    {
                                        hasExited = true;
                                        break;
                                    }
                                }
                                catch
                                {
                                    hasExited = true;
                                    break;
                                }
                            }
                        }

                        if (hasExited)
                        {
                            break;
                        }

                        // Try reading from the stream. < 0 means no read occurred.
                        readCount = await baseStream.ReadAsync(swapBuffer, 0, swapBuffer.Length, ct);

                        // When no read is done, we need to let is rest for a bit
                        if (readCount <= 0)
                        {
                            await Task.Delay(1, ct); // do not hog CPU cycles doing nothing.
                            continue;
                        }

                        totalCount += (ulong)readCount;
                        if (onDataCallback == null)
                        {
                            continue;
                        }

                        // Create the buffer to pass to the callback
                        var eventBuffer = swapBuffer.Skip(0).Take(readCount).ToArray();

                        // Create the data processing callback invocation
                        var eventTask =
                            Task.Factory.StartNew(() => { onDataCallback.Invoke(eventBuffer, process); }, ct);

                        // wait for the event to process before the next read occurs
                        if (syncEvents)
                        {
                            eventTask.Wait(ct);
                        }
                    }
                    catch
                    {
                        break;
                    }
                }

                return totalCount;
            }, ct).Unwrap());
        }
Пример #3
0
        /// <summary>
        /// Runs an external process asynchronously, providing callbacks to
        /// capture binary data from the standard error and standard output streams.
        /// The callbacks contain a reference to the process so you can respond to output or
        /// error streams by writing to the process' input stream.
        /// The exit code (return value) will be -1 for forceful termination of the process.
        /// </summary>
        /// <param name="filename">The filename.</param>
        /// <param name="arguments">The arguments.</param>
        /// <param name="workingDirectory">The working directory.</param>
        /// <param name="onOutputData">The on output data.</param>
        /// <param name="onErrorData">The on error data.</param>
        /// <param name="encoding">The encoding.</param>
        /// <param name="syncEvents">if set to <c>true</c> the next data callback will wait until the current one completes.</param>
        /// <param name="ct">The cancellation token.</param>
        /// <returns>
        /// Value type will be -1 for forceful termination of the process.
        /// </returns>
        public static Task <int> RunProcessAsync(
            string filename,
            string arguments,
            string workingDirectory,
            ProcessDataReceivedCallback onOutputData,
            ProcessDataReceivedCallback onErrorData,
            Encoding encoding,
            bool syncEvents      = true,
            CancellationToken ct = default)
        {
            if (filename == null)
            {
                throw new ArgumentNullException(nameof(filename));
            }

            return(Task.Factory.StartNew(() =>
            {
                // Setup the process and its corresponding start info
                var process = new Process
                {
                    EnableRaisingEvents = false,
                    StartInfo = new ProcessStartInfo
                    {
                        Arguments = arguments,
                        CreateNoWindow = true,
                        FileName = filename,
                        RedirectStandardError = true,
                        StandardErrorEncoding = encoding,
                        RedirectStandardOutput = true,
                        StandardOutputEncoding = encoding,
                        UseShellExecute = false,
#if NET452
                        WindowStyle = ProcessWindowStyle.Hidden,
#endif
                    },
                };

                if (!string.IsNullOrWhiteSpace(workingDirectory))
                {
                    process.StartInfo.WorkingDirectory = workingDirectory;
                }

                // Launch the process and discard any buffered data for standard error and standard output
                process.Start();
                process.StandardError.DiscardBufferedData();
                process.StandardOutput.DiscardBufferedData();

                // Launch the asynchronous stream reading tasks
                var readTasks = new Task[2];
                readTasks[0] = CopyStreamAsync(
                    process,
                    process.StandardOutput.BaseStream,
                    onOutputData,
                    syncEvents,
                    ct);
                readTasks[1] = CopyStreamAsync(
                    process,
                    process.StandardError.BaseStream,
                    onErrorData,
                    syncEvents,
                    ct);

                try
                {
                    // Wait for all tasks to complete
                    Task.WaitAll(readTasks, ct);
                }
                catch (TaskCanceledException)
                {
                    // ignore
                }
                finally
                {
                    // Wait for the process to exit
                    while (ct.IsCancellationRequested == false)
                    {
                        if (process.HasExited || process.WaitForExit(5))
                        {
                            break;
                        }
                    }

                    // Forcefully kill the process if it do not exit
                    try
                    {
                        if (process.HasExited == false)
                        {
                            process.Kill();
                        }
                    }
                    catch
                    {
                        // swallow
                    }
                }

                try
                {
                    // Retrieve and return the exit code.
                    // -1 signals error
                    return process.HasExited ? process.ExitCode : -1;
                }
                catch
                {
                    return -1;
                }
            }, ct));
        }