2008-11-12 29 views
112

Tôi cần phải sinh ra một tiến trình con là một ứng dụng giao diện điều khiển, và nắm bắt đầu ra của nó.Làm thế nào để sinh ra một quá trình và nắm bắt STDOUT của nó trong .NET?

tôi đã viết lên đoạn mã sau cho một phương pháp:

string retMessage = String.Empty; 
ProcessStartInfo startInfo = new ProcessStartInfo(); 
Process p = new Process(); 

startInfo.CreateNoWindow = true; 
startInfo.RedirectStandardOutput = true; 
startInfo.RedirectStandardInput = true; 

startInfo.UseShellExecute = false; 
startInfo.Arguments = command; 
startInfo.FileName = exec; 

p.StartInfo = startInfo; 
p.Start(); 

p.OutputDataReceived += new DataReceivedEventHandler 
(
    delegate(object sender, DataReceivedEventArgs e) 
    { 
     using (StreamReader output = p.StandardOutput) 
     { 
      retMessage = output.ReadToEnd(); 
     } 
    } 
); 

p.WaitForExit(); 

return retMessage; 

Tuy nhiên, điều này không trả lại bất cứ điều gì. Tôi không tin sự kiện OutputDataReceived đang được gọi lại hoặc lệnh WaitForExit() có thể đang chặn luồng để nó sẽ không bao giờ gọi lại.

Bạn có lời khuyên nào không?

EDIT: Có vẻ như tôi đang cố gắng hết sức với cuộc gọi lại. Làm:

return p.StandardOutput.ReadToEnd(); 

Xuất hiện để hoạt động tốt.

+1

Nếu bạn không tương tác với ứng dụng và chỉ quan tâm đến đầu ra của ứng dụng, bạn không nên sử dụng phương thức 'BeginOutputReadLine()' và 'Start()' của Async. Tôi đã tìm thấy những điều này là không đáng tin cậy, và đôi khi chúng có thể cắt bớt đầu của đầu ra của ứng dụng. –

+0

Tại sao không 'retMessage = e.Data'? Mà là một chuỗi var đã :) – Nickon

+14

@ FlySwat 7 năm sau đó, làm thế nào về đánh dấu một câu trả lời được chấp nhận? :-) –

Trả lời

3

Bạn cần gọi p.Start() để thực sự chạy quy trình sau khi bạn đặt StartInfo. Như vậy, chức năng của bạn có thể đang treo trên cuộc gọi WaitForExit() vì quá trình này chưa bao giờ thực sự bắt đầu.

+0

đó là lỗi đánh máy, vì tôi đã xóa một số mã để tạo mẫu, tôi vô tình cắt đường đó ra. – FlySwat

131

Đây là mã mà tôi đã xác minh để hoạt động. Tôi sử dụng nó để sinh ra MSBuild và lắng nghe đầu ra của nó:

process.StartInfo.UseShellExecute = false; 
process.StartInfo.RedirectStandardOutput = true; 
process.OutputDataReceived += (sender, args) => Console.WriteLine("received output: {0}", args.Data); 
process.Start(); 
process.BeginOutputReadLine(); 
+25

Lệnh quan trọng sửa lỗi OP là thêm BeginOutputReadLine() –

+1

Cảm ơn rất nhiều, @Judah Himango – Gezim

+4

Tôi không thể tin được có bao nhiêu người bỏ qua phần "BeginOutputReadLine". Đã lưu ngày của tôi, cảm ơn! –

19

Có vẻ như hai trong số các dòng của bạn đã hết hàng. Bạn bắt đầu quá trình trước khi thiết lập trình xử lý sự kiện để nắm bắt đầu ra. Có thể quá trình này chỉ kết thúc trước khi trình xử lý sự kiện được thêm vào.

Chuyển đổi các dòng như vậy.

p.OutputDataReceived += ... 
p.Start();   
+0

Mặc dù không được đánh dấu như vậy, đây có lẽ là câu trả lời đúng. –

+0

Điều này không hiệu quả đối với tôi. EDIT mà FlySwat bao gồm trong câu trả lời của ông làm việc cho tôi. –

+0

@CasperLeonNielsen, ditto. – as9876

29

Tôi chỉ cố gắng điều này rất và sau đây làm việc cho tôi:

StringBuilder outputBuilder; 
ProcessStartInfo processStartInfo; 
Process process; 

outputBuilder = new StringBuilder(); 

processStartInfo = new ProcessStartInfo(); 
processStartInfo.CreateNoWindow = true; 
processStartInfo.RedirectStandardOutput = true; 
processStartInfo.RedirectStandardInput = true; 
processStartInfo.UseShellExecute = false; 
processStartInfo.Arguments = "<insert command line arguments here>"; 
processStartInfo.FileName = "<insert tool path here>"; 

process = new Process(); 
process.StartInfo = processStartInfo; 
// enable raising events because Process does not raise events by default 
process.EnableRaisingEvents = true; 
// attach the event handler for OutputDataReceived before starting the process 
process.OutputDataReceived += new DataReceivedEventHandler 
(
    delegate(object sender, DataReceivedEventArgs e) 
    { 
     // append the new data to the data already read-in 
     outputBuilder.Append(e.Data); 
    } 
); 
// start the process 
// then begin asynchronously reading the output 
// then wait for the process to exit 
// then cancel asynchronously reading the output 
process.Start(); 
process.BeginOutputReadLine(); 
process.WaitForExit(); 
process.CancelOutputRead(); 

// use the output 
string output = outputBuilder.ToString(); 
+7

Tôi thấy điều này dễ dàng hơn một chút 'p.StartInfo = startInfo; p.Start(); output = p.StandardOutput.ReadToEnd(); p.WaitForExit(); ' – Spike

+2

@Spike, nhưng chạy đồng bộ. Nếu bạn cần nạp dữ liệu vào đầu vào tiêu chuẩn, thì điều này sẽ không hoạt động – Sebastian

+1

Lưu ý rằng EnableRaisingEvents = true xuất hiện chỉ cần cho sự kiện Process.Exited được nâng lên –

0

Dưới đây là một phương pháp mà tôi sử dụng để chạy một quá trình và được đầu ra và các lỗi của nó:

public static string ShellExecute(this string path, string command, TextWriter writer, params string[] arguments) 
    { 
     using (var process = Process.Start(new ProcessStartInfo { WorkingDirectory = path, FileName = command, Arguments = string.Join(" ", arguments), UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true })) 
     { 
      using (process.StandardOutput) 
      { 
       writer.WriteLine(process.StandardOutput.ReadToEnd()); 
      } 
      using (process.StandardError) 
      { 
       writer.WriteLine(process.StandardError.ReadToEnd()); 
      } 
     } 

     return path; 
    } 

Ví dụ:

@"E:\Temp\MyWorkingDirectory".ShellExecute(@"C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\svcutil.exe", Console.Out); 
+7

Đó có phải là một bế tắc đang chờ xảy ra? MSDN Docs nói rằng bạn có nguy cơ bị bế tắc nếu nghe cả đầu ra và lỗi cùng một lúc. Ứng dụng sẽ dừng nếu bộ đệm lỗi đầy, và đợi cho nó trống.Nhưng bạn không làm trống bộ đệm lỗi cho đến khi bộ đệm đầu ra kết thúc (mà nó sẽ không được như ứng dụng đang chờ bộ đệm lỗi) ... –

13

Đây là một số mã đầy đủ và đơn giản để làm điều này. Điều này làm việc tốt khi tôi sử dụng nó.

var processStartInfo = new ProcessStartInfo 
{ 
    FileName = @"C:\SomeProgram", 
    Arguments = "Arguments", 
    RedirectStandardOutput = true, 
    UseShellExecute = false 
}; 
var process = Process.Start(processStartInfo); 
var output = process.StandardOutput.ReadToEnd(); 
process.WaitForExit(); 

Lưu ý rằng điều này chỉ chụp tiêu chuẩn đầu ra; nó không nắm bắt được lỗi tiêu chuẩn. Nếu bạn muốn cả hai, hãy sử dụng this technique cho mỗi luồng.

+0

Cảm ơn bạn đã nhập mã tối thiểu nhưng hoàn chỉnh. –

+0

Cũng xin cảm ơn ghi chú. – wp78de

2

Chuyển hướng luồng không đồng bộ và có khả năng sẽ tiếp tục sau khi quá trình chấm dứt. Nó được đề cập bởi Umar để hủy bỏ sau khi chấm dứt quá trình process.CancelOutputRead(). Tuy nhiên có tiềm năng mất dữ liệu.

Đây là hoạt động đáng tin cậy cho tôi:

process.WaitForExit(...); 
... 
while (process.StandardOutput.EndOfStream == false) 
{ 
    Thread.Sleep(100); 
} 

Tôi không thử phương pháp này nhưng tôi thích những gợi ý từ Sly:

if (process.WaitForExit(timeout)) 
{ 
    process.WaitForExit(); 
} 
+3

Khi đọc đầu ra/lỗi dòng không đồng bộ gọi quá trình.WaitForExit() (phiên bản không có tham số) sau khi process.WaitForExit (thời gian chờ) trả về true. Điều này sẽ chặn cho đến khi kết thúc đầu ra/lỗi dòng đã đạt được. Xem các lưu ý ở đây để biết chi tiết: http://msdn.microsoft.com/en-us/library/ty0d8k56%28v=vs.110%29 – Sly

18

tôi cần phải chụp cả stdout và stderr và có nó hết thời gian nếu quá trình không thoát ra khi được mong đợi. Tôi đã đưa ra điều này:

Process process = new Process(); 
StringBuilder outputStringBuilder = new StringBuilder(); 

try 
{ 
process.StartInfo.FileName = exeFileName; 
process.StartInfo.WorkingDirectory = args.ExeDirectory; 
process.StartInfo.Arguments = args; 
process.StartInfo.RedirectStandardError = true; 
process.StartInfo.RedirectStandardOutput = true; 
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; 
process.StartInfo.CreateNoWindow = true; 
process.StartInfo.UseShellExecute = false; 
process.EnableRaisingEvents = false; 
process.OutputDataReceived += (sender, eventArgs) => outputStringBuilder.AppendLine(eventArgs.Data); 
process.ErrorDataReceived += (sender, eventArgs) => outputStringBuilder.AppendLine(eventArgs.Data); 
process.Start(); 
process.BeginOutputReadLine(); 
process.BeginErrorReadLine(); 
var processExited = process.WaitForExit(PROCESS_TIMEOUT); 

if (processExited == false) // we timed out... 
{ 
    process.Kill(); 
    throw new Exception("ERROR: Process took too long to finish"); 
} 
else if (process.ExitCode != 0) 
{ 
    var output = outputStringBuilder.ToString(); 
    var prefixMessage = ""; 

    throw new Exception("Process exited with non-zero exit code of: " + process.ExitCode + Environment.NewLine + 
    "Output from process: " + outputStringBuilder.ToString()); 
} 
} 
finally 
{     
process.Close(); 
} 

Tôi đang tạo đường ống stdout và stderr vào cùng một chuỗi, nhưng bạn có thể giữ nó riêng biệt nếu cần. Nó sử dụng các sự kiện, vì vậy nó sẽ xử lý chúng khi chúng đến (tôi tin). Tôi đã chạy thành công này và sẽ sớm kiểm tra khối lượng.

+0

Điều này phù hợp với tôi! cảm ơn – Raha

0

Câu trả lời từ Giu-đa đã không làm việc cho tôi (hoặc không hoàn thành) như các ứng dụng đã được thoát sau khi người đầu tiên BeginOutputReadLine();

này làm việc cho tôi như một đoạn hoàn chỉnh, đọc đầu ra liên tục của một ping:

 var process = new Process(); 
     process.StartInfo.FileName = "ping"; 
     process.StartInfo.Arguments = "google.com -t"; 
     process.StartInfo.RedirectStandardOutput = true; 
     process.StartInfo.UseShellExecute = false; 
     process.OutputDataReceived += (sender, a) => Console.WriteLine(a.Data); 
     process.Start(); 
     process.BeginOutputReadLine(); 
     process.WaitForExit(); 
Các vấn đề liên quan