ProcessStartInfo ค้างอยู่ที่“ WaitForExit” หรือไม่ ทำไม?


187

ฉันมีรหัสต่อไปนี้:

info = new System.Diagnostics.ProcessStartInfo("TheProgram.exe", String.Join(" ", args));
info.CreateNoWindow = true;
info.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
info.RedirectStandardOutput = true;
info.UseShellExecute = false;
System.Diagnostics.Process p = System.Diagnostics.Process.Start(info);
p.WaitForExit();
Console.WriteLine(p.StandardOutput.ReadToEnd()); //need the StandardOutput contents

ฉันรู้ว่าผลลัพธ์จากกระบวนการที่ฉันเริ่มมีความยาวประมาณ 7MB ใช้งานได้ในคอนโซล Windows ทำงานได้ดี น่าเสียดายที่โปรแกรมนี้แฮงค์อย่างไม่มีกำหนดเวลาที่ WaitForExit โปรดทราบว่านี่จะไม่ใช้รหัสเพื่อวางผลลัพธ์ขนาดเล็ก (เช่น 3KB)

เป็นไปได้ว่า StandardOutput ภายในใน ProcessStartInfo ไม่สามารถบัฟเฟอร์ 7MB ได้หรือไม่? ถ้าเป็นเช่นนั้นฉันควรทำอย่างไร ถ้าไม่ฉันทำอะไรผิด


ทางออกสุดท้ายที่มีซอร์สโค้ดเต็มเกี่ยวกับมัน
Kiquenet

2
ฉันพบปัญหาเดียวกันและนี่คือวิธีที่ฉันสามารถแก้ปัญหาได้stackoverflow.com/questions/2285288/ …
Bedasso

6
ใช่ทางออกสุดท้าย: สลับสองบรรทัดสุดท้าย มันอยู่ในคู่มือ
Amit Naidu

4
จาก msdn: ตัวอย่างรหัสหลีกเลี่ยงเงื่อนไขการหยุดชะงักโดยการเรียก p.StandardOutput.ReadToEnd ก่อน p.WaitForExit เงื่อนไขการหยุดชะงักอาจส่งผลหากกระบวนการหลักเรียก p.WaitForExit ก่อน p.StardardOutput.ReadToEnd และกระบวนการลูกเขียนข้อความเพียงพอที่จะเติมกระแสการเปลี่ยนเส้นทาง กระบวนการหลักจะรออย่างต่อเนื่องเพื่อให้กระบวนการเด็กออก กระบวนการลูกจะรออย่างต่อเนื่องเพื่อให้ผู้ปกครองที่จะอ่านจากกระแส StandardOutput เต็มรูปแบบ
Carlos Liu

มันค่อนข้างน่ารำคาญว่ามันซับซ้อนแค่ไหนในการทำอย่างถูกต้อง ยินดีที่จะทำงานรอบ ๆ มันมีคำสั่งง่ายเปลี่ยนเส้นทางสาย> outputfile :)
eglasius

คำตอบ:


393

ปัญหาคือว่าถ้าคุณเปลี่ยนเส้นทางStandardOutputและ / หรือStandardErrorบัฟเฟอร์ภายในอาจเต็ม ไม่ว่าคุณจะสั่งอะไรก็ตามอาจมีปัญหา:

  • หากคุณรอให้กระบวนการออกก่อนที่จะอ่านStandardOutputกระบวนการสามารถบล็อกพยายามเขียนมันดังนั้นกระบวนการไม่สิ้นสุด
  • ถ้าคุณอ่านจากการStandardOutputใช้ ReadToEnd กระบวนการของคุณสามารถบล็อกได้หากกระบวนการไม่เคยปิดStandardOutput(ตัวอย่างเช่นถ้ามันไม่เคยยุติหรือถ้ามันถูกบล็อกเขียนStandardError)

วิธีแก้ไขคือใช้อ่านแบบอะซิงโครนัสเพื่อให้แน่ใจว่าบัฟเฟอร์ไม่เต็ม ในการหลีกเลี่ยงการหยุดชะงักและรวบรวมเอาท์พุททั้งหมดจากทั้งคู่StandardOutputและStandardErrorคุณสามารถทำได้:

แก้ไข: ดูคำตอบด้านล่างเพื่อหลีกเลี่ยงObjectDisposedExceptionหากการหมดเวลาเกิดขึ้น

using (Process process = new Process())
{
    process.StartInfo.FileName = filename;
    process.StartInfo.Arguments = arguments;
    process.StartInfo.UseShellExecute = false;
    process.StartInfo.RedirectStandardOutput = true;
    process.StartInfo.RedirectStandardError = true;

    StringBuilder output = new StringBuilder();
    StringBuilder error = new StringBuilder();

    using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false))
    using (AutoResetEvent errorWaitHandle = new AutoResetEvent(false))
    {
        process.OutputDataReceived += (sender, e) => {
            if (e.Data == null)
            {
                outputWaitHandle.Set();
            }
            else
            {
                output.AppendLine(e.Data);
            }
        };
        process.ErrorDataReceived += (sender, e) =>
        {
            if (e.Data == null)
            {
                errorWaitHandle.Set();
            }
            else
            {
                error.AppendLine(e.Data);
            }
        };

        process.Start();

        process.BeginOutputReadLine();
        process.BeginErrorReadLine();

        if (process.WaitForExit(timeout) &&
            outputWaitHandle.WaitOne(timeout) &&
            errorWaitHandle.WaitOne(timeout))
        {
            // Process completed. Check process.ExitCode here.
        }
        else
        {
            // Timed out.
        }
    }
}

11
ไม่มีความคิดว่าการเปลี่ยนเส้นทางผลลัพธ์เป็นสาเหตุของปัญหา แต่ก็เพียงพอแล้ว ใช้เวลา 4 ชั่วโมงทุบหัวของฉันและแก้ไขมันใน 5 นาทีหลังจากอ่านโพสต์ของคุณ เยี่ยมมาก!
Ben Gripka

1
@AlexPeck ปัญหานี้ใช้เป็นแอพคอนโซล Hans Passant ระบุปัญหาที่นี่: stackoverflow.com/a/16218470/279516
Bob Horn

5
ทุกครั้งที่พรอมต์คำสั่งปิดปรากฏขึ้น: มีข้อยกเว้นที่ไม่สามารถจัดการได้ของชนิด "System.ObjectDisposed" เกิดขึ้นใน mscorlib.dll ข้อมูลเพิ่มเติม: ปิดที่จับอย่างปลอดภัยแล้ว
user1663380

3
เรามีปัญหาที่คล้ายกันตามที่อธิบายโดย @ user1663380 ด้านบน คุณคิดว่ามันเป็นไปได้ว่าusingงบสำหรับการจัดการเหตุการณ์จะต้องเหนือusingคำสั่งสำหรับกระบวนการของตัวเอง?
Dan Forbes

2
ฉันไม่คิดว่าจำเป็นต้องใช้มือจับรอ ตาม msdn เพียงแค่จบด้วย WaitoutExit รุ่นที่ไม่ใช่การหมดเวลา: เมื่อเอาต์พุตมาตรฐานถูกเปลี่ยนเส้นทางไปยังตัวจัดการเหตุการณ์แบบอะซิงโครนัสเป็นไปได้ที่การประมวลผลเอาต์พุตจะไม่เสร็จสมบูรณ์เมื่อเมธอดนี้ส่งคืน เพื่อให้แน่ใจว่าการจัดการเหตุการณ์แบบอะซิงโครนัสเสร็จสมบูรณ์แล้วให้เรียกใช้การโอเวอร์โหลดของ WaitForExit () ที่ไม่มีพารามิเตอร์หลังจากรับค่าจริงจากการโอเวอร์โหลดนี้
Patrick

98

เอกสารสำหรับการProcess.StandardOutputพูดการอ่านก่อนที่คุณจะรอมิฉะนั้นคุณจะหยุดชะงัก snippet คัดลอกด้านล่าง:

 // Start the child process.
 Process p = new Process();
 // Redirect the output stream of the child process.
 p.StartInfo.UseShellExecute = false;
 p.StartInfo.RedirectStandardOutput = true;
 p.StartInfo.FileName = "Write500Lines.exe";
 p.Start();
 // Do not wait for the child process to exit before
 // reading to the end of its redirected stream.
 // p.WaitForExit();
 // Read the output stream first and then wait.
 string output = p.StandardOutput.ReadToEnd();
 p.WaitForExit();

14
ฉันไม่แน่ใจ 100% หากนี่เป็นเพียงผลจากสภาพแวดล้อมของฉัน แต่ฉันพบว่าคุณได้ตั้งค่าไว้RedirectStandardOutput = true;แล้วและไม่ใช้p.StandardOutput.ReadToEnd();คุณจะได้รับการหยุดชะงัก / หยุดทำงาน
Chris S

3
จริง ฉันอยู่ในสถานการณ์ที่คล้ายคลึงกัน ฉันเปลี่ยนเส้นทาง StandardError โดยไม่มีเหตุผลเมื่อทำการแปลงด้วย ffmpeg ในกระบวนการมันเขียนพอในสตรีม StandardError เพื่อสร้างการหยุดชะงัก
Léon Pelletier

สิ่งนี้ยังคงค้างอยู่กับฉันแม้จะเปลี่ยนเส้นทางและอ่านเอาต์พุตมาตรฐาน
3791372

@ user3791372 ฉันเดาว่านี่จะใช้ได้เฉพาะถ้าบัฟเฟอร์ที่อยู่เบื้องหลัง StandardOutput ไม่ได้รับการเติมเต็ม ที่นี่ MSDN ไม่ได้ทำเพื่อความยุติธรรม บทความที่ดีที่ผมจะแนะนำให้คุณอ่านได้ที่: dzone.com/articles/async-io-and-threadpool
Cary

19

คำตอบของ Mark Byers นั้นยอดเยี่ยม แต่ฉันจะเพิ่มสิ่งต่อไปนี้:

OutputDataReceivedและErrorDataReceivedผู้ได้รับมอบหมายจะต้องถูกลบออกก่อนoutputWaitHandleและerrorWaitHandleได้รับการจำหน่าย หากกระบวนการยังคงส่งออกข้อมูลหลังจากหมดเวลาเกินแล้วจะสิ้นสุดลงoutputWaitHandleและerrorWaitHandleตัวแปรจะสามารถเข้าถึงได้หลังจากถูกกำจัด

(FYI ฉันต้องเพิ่มคำเตือนนี้เป็นคำตอบเนื่องจากฉันไม่สามารถแสดงความคิดเห็นในโพสต์ของเขาได้)


2
บางทีมันอาจจะดีกว่าที่จะเรียกCancelOutputRead ?
Mark Byers

การเพิ่มรหัสที่แก้ไขแล้วของ Mark ในคำตอบนี้จะยอดเยี่ยมมาก! ฉันมีปัญหาเดียวกันที่แน่นอนในนาที
ianbailey

8
@ianbailey วิธีที่ง่ายที่สุดในการแก้ปัญหานี้คือการใส่การใช้ (กระบวนการ p ... ) ไว้ข้างในการใช้ (ข้อผิดพลาด AutoResetEventWaitHandle ... )
Didier A.

18

นี่เป็นวิธีการแก้ปัญหาที่ทันสมัยมากขึ้นซึ่งใช้งาน Task Parallel Library (TPL) สำหรับ. NET 4.5 และสูงกว่า

ตัวอย่างการใช้งาน

try
{
    var exitCode = await StartProcess(
        "dotnet", 
        "--version", 
        @"C:\",
        10000, 
        Console.Out, 
        Console.Out);
    Console.WriteLine($"Process Exited with Exit Code {exitCode}!");
}
catch (TaskCanceledException)
{
    Console.WriteLine("Process Timed Out!");
}

การดำเนินงาน

public static async Task<int> StartProcess(
    string filename,
    string arguments,
    string workingDirectory= null,
    int? timeout = null,
    TextWriter outputTextWriter = null,
    TextWriter errorTextWriter = null)
{
    using (var process = new Process()
    {
        StartInfo = new ProcessStartInfo()
        {
            CreateNoWindow = true,
            Arguments = arguments,
            FileName = filename,
            RedirectStandardOutput = outputTextWriter != null,
            RedirectStandardError = errorTextWriter != null,
            UseShellExecute = false,
            WorkingDirectory = workingDirectory
        }
    })
    {
        var cancellationTokenSource = timeout.HasValue ?
            new CancellationTokenSource(timeout.Value) :
            new CancellationTokenSource();

        process.Start();

        var tasks = new List<Task>(3) { process.WaitForExitAsync(cancellationTokenSource.Token) };
        if (outputTextWriter != null)
        {
            tasks.Add(ReadAsync(
                x =>
                {
                    process.OutputDataReceived += x;
                    process.BeginOutputReadLine();
                },
                x => process.OutputDataReceived -= x,
                outputTextWriter,
                cancellationTokenSource.Token));
        }

        if (errorTextWriter != null)
        {
            tasks.Add(ReadAsync(
                x =>
                {
                    process.ErrorDataReceived += x;
                    process.BeginErrorReadLine();
                },
                x => process.ErrorDataReceived -= x,
                errorTextWriter,
                cancellationTokenSource.Token));
        }

        await Task.WhenAll(tasks);
        return process.ExitCode;
    }
}

/// <summary>
/// Waits asynchronously for the process to exit.
/// </summary>
/// <param name="process">The process to wait for cancellation.</param>
/// <param name="cancellationToken">A cancellation token. If invoked, the task will return
/// immediately as cancelled.</param>
/// <returns>A Task representing waiting for the process to end.</returns>
public static Task WaitForExitAsync(
    this Process process,
    CancellationToken cancellationToken = default(CancellationToken))
{
    process.EnableRaisingEvents = true;

    var taskCompletionSource = new TaskCompletionSource<object>();

    EventHandler handler = null;
    handler = (sender, args) =>
    {
        process.Exited -= handler;
        taskCompletionSource.TrySetResult(null);
    };
    process.Exited += handler;

    if (cancellationToken != default(CancellationToken))
    {
        cancellationToken.Register(
            () =>
            {
                process.Exited -= handler;
                taskCompletionSource.TrySetCanceled();
            });
    }

    return taskCompletionSource.Task;
}

/// <summary>
/// Reads the data from the specified data recieved event and writes it to the
/// <paramref name="textWriter"/>.
/// </summary>
/// <param name="addHandler">Adds the event handler.</param>
/// <param name="removeHandler">Removes the event handler.</param>
/// <param name="textWriter">The text writer.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public static Task ReadAsync(
    this Action<DataReceivedEventHandler> addHandler,
    Action<DataReceivedEventHandler> removeHandler,
    TextWriter textWriter,
    CancellationToken cancellationToken = default(CancellationToken))
{
    var taskCompletionSource = new TaskCompletionSource<object>();

    DataReceivedEventHandler handler = null;
    handler = new DataReceivedEventHandler(
        (sender, e) =>
        {
            if (e.Data == null)
            {
                removeHandler(handler);
                taskCompletionSource.TrySetResult(null);
            }
            else
            {
                textWriter.WriteLine(e.Data);
            }
        });

    addHandler(handler);

    if (cancellationToken != default(CancellationToken))
    {
        cancellationToken.Register(
            () =>
            {
                removeHandler(handler);
                taskCompletionSource.TrySetCanceled();
            });
    }

    return taskCompletionSource.Task;
}

2
คำตอบที่ดีที่สุดและสมบูรณ์ที่สุดถึงวันที่
TermoTux

1
สำหรับ reaon นี่เป็นทางออกเดียวที่เหมาะกับฉันแอปพลิเคชันหยุดการแฮงค์
แจ็ค

1
ดูเหมือนว่าคุณไม่ได้จัดการกับเงื่อนไขที่กระบวนการสิ้นสุดหลังจากเริ่มต้น แต่ก่อนที่จะแนบเหตุการณ์ Exited คำแนะนำของฉัน - เริ่มต้นกระบวนการหลังจากการลงทะเบียนทั้งหมด
Stas Boyarincev

@StasBoyarincev ขอบคุณอัปเดตแล้ว ฉันลืมปรับปรุงคำตอบ StackOverflow ด้วยการเปลี่ยนแปลงนี้
มูฮัมหมัด

1
@ MuhammadRehanSaeed อีกสิ่งหนึ่ง - ดูเหมือนว่าจะไม่อนุญาตให้มีการเรียกกระบวนการเริ่มต้น BeginOutputReadLine () หรือกระบวนการ BeginErrorReadLine () ก่อนเริ่มกระบวนการ ในกรณีนี้ฉันได้รับข้อผิดพลาด: StandardOut ยังไม่ได้เปลี่ยนเส้นทางหรือกระบวนการยังไม่เริ่ม
Stas Boyarincev

17

ปัญหาเกี่ยวกับ ObjectDisposedException ที่ไม่ได้จัดการเกิดขึ้นเมื่อกระบวนการหมดเวลา ในกรณีเช่นนี้ส่วนอื่น ๆ ของเงื่อนไข:

if (process.WaitForExit(timeout) 
    && outputWaitHandle.WaitOne(timeout) 
    && errorWaitHandle.WaitOne(timeout))

จะไม่ถูกดำเนินการ ฉันแก้ไขปัญหานี้ด้วยวิธีต่อไปนี้:

using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false))
using (AutoResetEvent errorWaitHandle = new AutoResetEvent(false))
{
    using (Process process = new Process())
    {
        // preparing ProcessStartInfo

        try
        {
            process.OutputDataReceived += (sender, e) =>
                {
                    if (e.Data == null)
                    {
                        outputWaitHandle.Set();
                    }
                    else
                    {
                        outputBuilder.AppendLine(e.Data);
                    }
                };
            process.ErrorDataReceived += (sender, e) =>
                {
                    if (e.Data == null)
                    {
                        errorWaitHandle.Set();
                    }
                    else
                    {
                        errorBuilder.AppendLine(e.Data);
                    }
                };

            process.Start();

            process.BeginOutputReadLine();
            process.BeginErrorReadLine();

            if (process.WaitForExit(timeout))
            {
                exitCode = process.ExitCode;
            }
            else
            {
                // timed out
            }

            output = outputBuilder.ToString();
        }
        finally
        {
            outputWaitHandle.WaitOne(timeout);
            errorWaitHandle.WaitOne(timeout);
        }
    }
}

1
เพื่อความครบถ้วนสมบูรณ์นี้จะหายไปในการตั้งค่าการเปลี่ยนเส้นทางให้เป็นจริง
knocte

และฉันได้ลบการหมดเวลาในตอนท้ายเนื่องจากกระบวนการอาจขอให้ผู้ใช้ป้อนข้อมูล (เช่นพิมพ์บางอย่าง) ดังนั้นฉันจึงไม่ต้องการให้ผู้ใช้รวดเร็ว
knocte

ทำไมคุณจึงมีการเปลี่ยนแปลงoutputและerrorเพื่อoutputBuilder? ใครช่วยกรุณาให้คำตอบที่สมบูรณ์ใช้งานได้?
Marko Avlijaš

System.ObjectDisposedException: การจัดการที่ปลอดภัยถูกปิดแล้วเกิดขึ้นกับรุ่นนี้สำหรับฉันเช่นกัน
Matt

8

Rob ตอบคำถามนั้นและช่วยฉันทดลองอีกไม่กี่ชั่วโมง อ่านบัฟเฟอร์เอาต์พุต / ข้อผิดพลาดก่อนรอ:

// Read the output stream first and then wait.
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();

1
แต่ถ้าข้อมูลได้มากขึ้นมาหลังจากที่คุณได้เรียกว่าWaitForExit()?
knocte

@ Knocte ตามการทดสอบของฉันReadToEndหรือวิธีการที่คล้ายกัน (เช่นStandardOutput.BaseStream.CopyTo) จะกลับมาหลังจากอ่านข้อมูลทั้งหมด จะไม่มีอะไรเกิดขึ้นหลังจากนั้น
S. Sereroshan

คุณกำลังบอกว่า ReadToEnd () ยังรอทางออกหรือไม่
knocte

2
@ Knocte คุณพยายามทำความเข้าใจกับ API ที่สร้างโดย microsoft หรือไม่
aaaaaa

ปัญหาของหน้า MSDN ที่เกี่ยวข้องคือมันไม่ได้อธิบายว่าบัฟเฟอร์ที่อยู่เบื้องหลัง StandardOutput อาจเต็มและในสถานการณ์นั้นเด็กจะต้องหยุดเขียนและรอจนกว่าบัฟเฟอร์จะถูกระบายออก (ผู้ปกครองอ่านข้อมูลในบัฟเฟอร์) . ReadToEnd () สามารถซิงค์ได้อย่างเดียวอ่านจนกว่าบัฟเฟอร์จะปิดหรือบัฟเฟอร์เต็มหรือมีลูกออกจากบัฟเฟอร์ไม่เต็ม นั่นคือความเข้าใจของฉัน
นิวเคลียส

7

เรามีปัญหานี้เช่นกัน (หรือตัวแปร)

ลองทำสิ่งต่อไปนี้:

1) เพิ่มการหมดเวลาใน p.WaitForExit (nnnn); โดยที่ nnnn อยู่ในหน่วยมิลลิวินาที

2) วางสาย ReadToEnd ก่อนการเรียก WaitForExit นี่คือสิ่งที่เราเคยเห็น MS แนะนำ


5

ให้เครดิตแก่EM0สำหรับhttps://stackoverflow.com/a/17600012/4151626

โซลูชันอื่น ๆ (รวมถึง EM0) ยังคงถูกปิดกั้นสำหรับแอปพลิเคชันของฉันเนื่องจากการหมดเวลาภายในและการใช้ทั้ง StandardOutput และ StandardError โดยแอปพลิเคชันที่ได้รับมา นี่คือสิ่งที่ทำงานให้ฉัน:

Process p = new Process()
{
  StartInfo = new ProcessStartInfo()
  {
    FileName = exe,
    Arguments = args,
    UseShellExecute = false,
    RedirectStandardOutput = true,
    RedirectStandardError = true
  }
};
p.Start();

string cv_error = null;
Thread et = new Thread(() => { cv_error = p.StandardError.ReadToEnd(); });
et.Start();

string cv_out = null;
Thread ot = new Thread(() => { cv_out = p.StandardOutput.ReadToEnd(); });
ot.Start();

p.WaitForExit();
ot.Join();
et.Join();

แก้ไข: เพิ่มการเริ่มต้นของ StartInfo ลงในตัวอย่างโค้ด


นี่คือสิ่งที่ฉันใช้และไม่เคยมีปัญหาอีกต่อไปกับการหยุดชะงัก
Roemer

3

ฉันแก้ไขมันด้วยวิธีนี้:

            Process proc = new Process();
            proc.StartInfo.FileName = batchFile;
            proc.StartInfo.UseShellExecute = false;
            proc.StartInfo.CreateNoWindow = true;
            proc.StartInfo.RedirectStandardError = true;
            proc.StartInfo.RedirectStandardInput = true;
            proc.StartInfo.RedirectStandardOutput = true;
            proc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;      
            proc.Start();
            StreamWriter streamWriter = proc.StandardInput;
            StreamReader outputReader = proc.StandardOutput;
            StreamReader errorReader = proc.StandardError;
            while (!outputReader.EndOfStream)
            {
                string text = outputReader.ReadLine();                    
                streamWriter.WriteLine(text);
            }

            while (!errorReader.EndOfStream)
            {                   
                string text = errorReader.ReadLine();
                streamWriter.WriteLine(text);
            }

            streamWriter.Close();
            proc.WaitForExit();

ฉันเปลี่ยนเส้นทางทั้งอินพุตเอาต์พุตและข้อผิดพลาดและจัดการการอ่านจากเอาต์พุตและสตรีมข้อผิดพลาด วิธีนี้ใช้ได้กับ SDK 7- 8.1 ทั้งสำหรับ Windows 7 และ Windows 8


2
Elina: ขอบคุณสำหรับคำตอบของคุณ มีหมายเหตุบางส่วนที่ด้านล่างของเอกสาร MSDN นี้ ( msdn.microsoft.com/en-us/library/ ...... ) ที่เตือนเกี่ยวกับการหยุดชะงักที่อาจเกิดขึ้นหากคุณอ่านจนจบทั้ง stdout และ stderr สตรีมที่เปลี่ยนเส้นทางพร้อมกัน เป็นการยากที่จะบอกว่าโซลูชันของคุณไวต่อปัญหานี้หรือไม่ นอกจากนี้ยังปรากฏว่าคุณกำลังส่งเอาต์พุต 'stdout / stderr ของกระบวนการทันทีในฐานะอินพุต ทำไม? :)
Matthew Piatt

3

ฉันพยายามสร้างคลาสที่จะแก้ปัญหาของคุณโดยใช้สตรีมแบบอะซิงโครนัสอ่านโดยคำนึงถึง Mark Byers, Rob, stevejay การทำเช่นนั้นฉันรู้ว่ามีข้อผิดพลาดที่เกี่ยวข้องกับการอ่านสตรีมเอาท์พุทกระบวนการแบบอะซิงโครนัส

ฉันรายงานข้อผิดพลาดที่ Microsoft: https://connect.microsoft.com/VisualStudio/feedback/details/3119134

สรุป:

คุณทำเช่นนั้นไม่ได้:

process.BeginOutputReadLine (); Process.Start ();

คุณจะได้รับ System.InvalidOperationException: StandardOut ยังไม่ถูกเปลี่ยนเส้นทางหรือกระบวนการยังไม่ได้เริ่ม

================================================== ================================================== ========================

จากนั้นคุณต้องเริ่มต้นเอาต์พุตอะซิงโครนัสที่อ่านหลังจากกระบวนการเริ่มต้น:

Process.Start (); process.BeginOutputReadLine ();

ให้ทำเงื่อนไขการแย่งชิงเนื่องจากเอาต์พุตสตรีมสามารถรับข้อมูลก่อนที่คุณจะตั้งค่าเป็นแบบอะซิงโครนัส:

process.Start(); 
// Here the operating system could give the cpu to another thread.  
// For example, the newly created thread (Process) and it could start writing to the output
// immediately before next line would execute. 
// That create a race condition.
process.BeginOutputReadLine();

================================================== ================================================== ========================

จากนั้นบางคนอาจบอกว่าคุณต้องอ่านสตรีมก่อนที่จะตั้งเป็นอะซิงโครนัส แต่ปัญหาเดียวกันเกิดขึ้น จะมีเงื่อนไขการแย่งชิงระหว่างการอ่านแบบซิงโครนัสและตั้งค่าสตรีมเข้าสู่โหมดอะซิงโครนัส

================================================== ================================================== ========================

ไม่มีวิธีใดที่จะทำให้การอ่านแบบอะซิงโครนัสปลอดภัยของเอาต์พุตสตรีมของกระบวนการในแบบ "กระบวนการ" และ "ProcessStartInfo" ได้รับการออกแบบ

คุณน่าจะใช้การอ่านแบบอะซิงโครนัสดีกว่าตามที่ผู้ใช้รายอื่นแนะนำสำหรับกรณีของคุณ แต่คุณควรระวังว่าคุณอาจพลาดข้อมูลบางอย่างเนื่องจากสภาพการแข่งขัน


1

ฉันว่านี่เป็นวิธีที่ง่ายและดีกว่า (เราไม่ต้องการAutoResetEvent):

public static string GGSCIShell(string Path, string Command)
{
    using (Process process = new Process())
    {
        process.StartInfo.WorkingDirectory = Path;
        process.StartInfo.FileName = Path + @"\ggsci.exe";
        process.StartInfo.CreateNoWindow = true;
        process.StartInfo.RedirectStandardOutput = true;
        process.StartInfo.RedirectStandardInput = true;
        process.StartInfo.UseShellExecute = false;

        StringBuilder output = new StringBuilder();
        process.OutputDataReceived += (sender, e) =>
        {
            if (e.Data != null)
            {
                output.AppendLine(e.Data);
            }
        };

        process.Start();
        process.StandardInput.WriteLine(Command);
        process.BeginOutputReadLine();


        int timeoutParts = 10;
        int timeoutPart = (int)TIMEOUT / timeoutParts;
        do
        {
            Thread.Sleep(500);//sometimes halv scond is enough to empty output buff (therefore "exit" will be accepted without "timeoutPart" waiting)
            process.StandardInput.WriteLine("exit");
            timeoutParts--;
        }
        while (!process.WaitForExit(timeoutPart) && timeoutParts > 0);

        if (timeoutParts <= 0)
        {
            output.AppendLine("------ GGSCIShell TIMEOUT: " + TIMEOUT + "ms ------");
        }

        string result = output.ToString();
        return result;
    }
}

จริง แต่คุณไม่ควรทำ.FileName = Path + @"\ggsci.exe" + @" < obeycommand.txt"เพื่อลดความซับซ้อนของรหัสของคุณด้วย? หรืออาจเทียบเท่ากับ"echo command | " + Path + @"\ggsci.exe"หากคุณไม่ต้องการใช้ไฟล์ obeycommand.txt แยกต่างหาก
Amit Naidu

3
โซลูชันของคุณไม่จำเป็นต้องใช้ AutoResetEvent แต่คุณสำรวจความคิดเห็น เมื่อคุณทำแบบสำรวจความคิดเห็นแทนการใช้เหตุการณ์ (เมื่อมีให้) คุณจะใช้ CPU โดยไม่มีเหตุผลและนั่นแสดงว่าคุณเป็นโปรแกรมเมอร์ที่ไม่ดี โซลูชันของคุณไม่ดีจริง ๆ เมื่อเปรียบเทียบกับที่อื่นโดยใช้ AutoResetEvent (แต่ฉันไม่ได้ให้ -1 เพราะคุณพยายามช่วย!)
Eric Ouellet

1

ไม่มีคำตอบข้างต้นที่กำลังทำงานอยู่

โซลูชันของ Rob หยุดแฮงค์และ 'Mark Byers' ได้รับการยกเว้น (ฉันลอง "คำตอบ" ของคำตอบอื่น ๆ )

ดังนั้นฉันตัดสินใจที่จะแนะนำวิธีแก้ปัญหาอื่น:

public void GetProcessOutputWithTimeout(Process process, int timeoutSec, CancellationToken token, out string output, out int exitCode)
{
    string outputLocal = "";  int localExitCode = -1;
    var task = System.Threading.Tasks.Task.Factory.StartNew(() =>
    {
        outputLocal = process.StandardOutput.ReadToEnd();
        process.WaitForExit();
        localExitCode = process.ExitCode;
    }, token);

    if (task.Wait(timeoutSec, token))
    {
        output = outputLocal;
        exitCode = localExitCode;
    }
    else
    {
        exitCode = -1;
        output = "";
    }
}

using (var process = new Process())
{
    process.StartInfo = ...;
    process.Start();
    string outputUnicode; int exitCode;
    GetProcessOutputWithTimeout(process, PROCESS_TIMEOUT, out outputUnicode, out exitCode);
}

รหัสนี้ดีบั๊กและทำงานได้อย่างสมบูรณ์


1
ดี! เพียงแค่ทราบว่าไม่มีการระบุพารามิเตอร์โทเค็นเมื่อเรียกGetProcessOutputWithTimeoutเมธอด
S.Serpooshan

1

บทนำ

คำตอบที่ได้รับการยอมรับในปัจจุบันไม่สามารถใช้งานได้ (มีข้อยกเว้น) และมีวิธีแก้ไขปัญหามากเกินไป แต่ไม่มีรหัสที่สมบูรณ์ เห็นได้ชัดว่าเป็นการสูญเสียเวลาของผู้คนจำนวนมากเพราะนี่เป็นคำถามยอดนิยม

การรวมคำตอบของ Mark Byers เข้ากับคำตอบของ Karol Tyl ฉันเขียนโค้ดแบบเต็มตามวิธีที่ฉันต้องการใช้เมธอด Process.Start

การใช้

ฉันใช้มันเพื่อสร้างกล่องโต้ตอบความคืบหน้าเกี่ยวกับคำสั่ง git นี่คือวิธีที่ฉันใช้:

    private bool Run(string fullCommand)
    {
        Error = "";
        int timeout = 5000;

        var result = ProcessNoBS.Start(
            filename: @"C:\Program Files\Git\cmd\git.exe",
            arguments: fullCommand,
            timeoutInMs: timeout,
            workingDir: @"C:\test");

        if (result.hasTimedOut)
        {
            Error = String.Format("Timeout ({0} sec)", timeout/1000);
            return false;
        }

        if (result.ExitCode != 0)
        {
            Error = (String.IsNullOrWhiteSpace(result.stderr)) 
                ? result.stdout : result.stderr;
            return false;
        }

        return true;
    }

ในทางทฤษฎีคุณสามารถรวม stdout และ stderr แต่ฉันยังไม่ได้ทดสอบ

รหัส

public struct ProcessResult
{
    public string stdout;
    public string stderr;
    public bool hasTimedOut;
    private int? exitCode;

    public ProcessResult(bool hasTimedOut = true)
    {
        this.hasTimedOut = hasTimedOut;
        stdout = null;
        stderr = null;
        exitCode = null;
    }

    public int ExitCode
    {
        get 
        {
            if (hasTimedOut)
                throw new InvalidOperationException(
                    "There was no exit code - process has timed out.");

            return (int)exitCode;
        }
        set
        {
            exitCode = value;
        }
    }
}

public class ProcessNoBS
{
    public static ProcessResult Start(string filename, string arguments,
        string workingDir = null, int timeoutInMs = 5000,
        bool combineStdoutAndStderr = false)
    {
        using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false))
        using (AutoResetEvent errorWaitHandle = new AutoResetEvent(false))
        {
            using (var process = new Process())
            {
                var info = new ProcessStartInfo();

                info.CreateNoWindow = true;
                info.FileName = filename;
                info.Arguments = arguments;
                info.UseShellExecute = false;
                info.RedirectStandardOutput = true;
                info.RedirectStandardError = true;

                if (workingDir != null)
                    info.WorkingDirectory = workingDir;

                process.StartInfo = info;

                StringBuilder stdout = new StringBuilder();
                StringBuilder stderr = combineStdoutAndStderr
                    ? stdout : new StringBuilder();

                var result = new ProcessResult();

                try
                {
                    process.OutputDataReceived += (sender, e) =>
                    {
                        if (e.Data == null)
                            outputWaitHandle.Set();
                        else
                            stdout.AppendLine(e.Data);
                    };
                    process.ErrorDataReceived += (sender, e) =>
                    {
                        if (e.Data == null)
                            errorWaitHandle.Set();
                        else
                            stderr.AppendLine(e.Data);
                    };

                    process.Start();

                    process.BeginOutputReadLine();
                    process.BeginErrorReadLine();

                    if (process.WaitForExit(timeoutInMs))
                        result.ExitCode = process.ExitCode;
                    // else process has timed out 
                    // but that's already default ProcessResult

                    result.stdout = stdout.ToString();
                    if (combineStdoutAndStderr)
                        result.stderr = null;
                    else
                        result.stderr = stderr.ToString();

                    return result;
                }
                finally
                {
                    outputWaitHandle.WaitOne(timeoutInMs);
                    errorWaitHandle.WaitOne(timeoutInMs);
                }
            }
        }
    }
}

ยังคงได้รับ System.ObjectDisposedException: หมายเลขอ้างอิงที่ปลอดภัยถูกปิดในรุ่นนี้ด้วย
Matt

1

ฉันรู้ว่านี่เป็นอาหารมื้อเย็นที่เก่า แต่หลังจากอ่านหน้านี้ทั้งหมดไม่มีวิธีแก้ปัญหาใดที่เหมาะกับฉันแม้ว่าฉันจะไม่ได้ลอง Muhammad Rehan เพราะรหัสนั้นยากที่จะติดตาม แต่ฉันเดาว่าเขากำลังอยู่ในเส้นทางที่ถูกต้อง . เมื่อฉันบอกว่ามันใช้งานไม่ได้มันไม่เป็นความจริง แต่บางครั้งมันก็ใช้ได้ดีฉันเดาว่ามันเป็นเรื่องเกี่ยวกับความยาวของเอาต์พุตก่อนเครื่องหมาย EOF

อย่างไรก็ตามวิธีแก้ปัญหาที่ใช้งานได้สำหรับฉันคือใช้เธรดที่แตกต่างกันเพื่ออ่าน StandardOutput และ StandardError และเขียนข้อความ

        StreamWriter sw = null;
        var queue = new ConcurrentQueue<string>();

        var flushTask = new System.Timers.Timer(50);
        flushTask.Elapsed += (s, e) =>
        {
            while (!queue.IsEmpty)
            {
                string line = null;
                if (queue.TryDequeue(out line))
                    sw.WriteLine(line);
            }
            sw.FlushAsync();
        };
        flushTask.Start();

        using (var process = new Process())
        {
            try
            {
                process.StartInfo.FileName = @"...";
                process.StartInfo.Arguments = $"...";
                process.StartInfo.UseShellExecute = false;
                process.StartInfo.RedirectStandardOutput = true;
                process.StartInfo.RedirectStandardError = true;

                process.Start();

                var outputRead = Task.Run(() =>
                {
                    while (!process.StandardOutput.EndOfStream)
                    {
                        queue.Enqueue(process.StandardOutput.ReadLine());
                    }
                });

                var errorRead = Task.Run(() =>
                {
                    while (!process.StandardError.EndOfStream)
                    {
                        queue.Enqueue(process.StandardError.ReadLine());
                    }
                });

                var timeout = new TimeSpan(hours: 0, minutes: 10, seconds: 0);

                if (Task.WaitAll(new[] { outputRead, errorRead }, timeout) &&
                    process.WaitForExit((int)timeout.TotalMilliseconds))
                {
                    if (process.ExitCode != 0)
                    {
                        throw new Exception($"Failed run... blah blah");
                    }
                }
                else
                {
                    throw new Exception($"process timed out after waiting {timeout}");
                }
            }
            catch (Exception e)
            {
                throw new Exception($"Failed to succesfully run the process.....", e);
            }
        }
    }

หวังว่านี่จะช่วยใครซักคนที่คิดว่าสิ่งนี้จะยากมาก!


ข้อยกเว้น: sw.FlushAsync(): Object is not set to an instance of an object. sw is null. ควรswกำหนดไว้อย่างไร/ ที่ไหน?
wallyk

1

หลังจากอ่านโพสต์ทั้งหมดที่นี่ฉันตัดสินโซลูชันรวมของ Marko Avlijaš อย่างไรก็ตามมันไม่ได้แก้ปัญหาทั้งหมดของฉัน

ในสภาพแวดล้อมของเราเรามีบริการ Windows ซึ่งมีกำหนดให้เรียกใช้ไฟล์. bat .cmd .exe, ... ฯลฯ ที่แตกต่างกันหลายร้อยไฟล์ซึ่งสะสมในช่วงหลายปีที่ผ่านมาและเขียนโดยคนหลายคน เราไม่สามารถควบคุมการเขียนโปรแกรมและสคริปต์ได้เรามีหน้าที่รับผิดชอบในการจัดตารางการทำงานและการรายงานเกี่ยวกับความสำเร็จ / ความล้มเหลว

ดังนั้นฉันจึงลองใช้คำแนะนำทั้งหมดที่นี่พร้อมด้วยระดับความสำเร็จที่แตกต่างกัน คำตอบของ Marko นั้นเกือบจะสมบูรณ์แบบ แต่เมื่อทำงานเป็นบริการมันไม่ได้จับ stdout เสมอไป ฉันไม่เคยไปถึงจุดต่ำสุดของทำไมไม่

ทางออกเดียวที่เราพบว่าใช้งานได้ในทุกกรณีของเราคือ: http://csharptest.net/319/using-the-processrunner-class/index.html


ฉันจะลองห้องสมุดนี้ ฉันกำหนดขอบเขตโค้ดและดูเหมือนว่าจะใช้ผู้ได้รับมอบหมายอย่างสมเหตุสมผล มันบรรจุอยู่ใน Nuget มันเป็นเรื่องของความเป็นมืออาชีพซึ่งเป็นสิ่งที่ฉันไม่อาจถูกกล่าวหาได้ ถ้ามันกัดจะบอก
Steve Hibbert

ลิงก์ไปยังซอร์สโค้ดนั้นตายแล้ว โปรดคัดลอกรหัสในครั้งต่อไปเพื่อรับคำตอบ
Vitaly Zdanevich

1

วิธีแก้ปัญหาฉันใช้เพื่อหลีกเลี่ยงความซับซ้อนทั้งหมด:

var outputFile = Path.GetTempFileName();
info = new System.Diagnostics.ProcessStartInfo("TheProgram.exe", String.Join(" ", args) + " > " + outputFile + " 2>&1");
info.CreateNoWindow = true;
info.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
info.UseShellExecute = false;
System.Diagnostics.Process p = System.Diagnostics.Process.Start(info);
p.WaitForExit();
Console.WriteLine(File.ReadAllText(outputFile)); //need the StandardOutput contents

ดังนั้นฉันจึงสร้างไฟล์ชั่วคราวเปลี่ยนเส้นทางทั้งผลลัพธ์และข้อผิดพลาดไปที่มันโดยใช้ > outputfile > 2>&1จากนั้นอ่านไฟล์หลังจากกระบวนการเสร็จสิ้น

โซลูชันอื่นนั้นใช้ได้ผลดีสำหรับสถานการณ์ที่คุณต้องการทำสิ่งอื่น ๆ ด้วยผลลัพธ์ แต่สำหรับสิ่งที่เรียบง่ายสิ่งนี้จะช่วยหลีกเลี่ยงความซับซ้อนมากมาย


1

ฉันได้อ่านคำตอบมากมายและทำด้วยตัวเอง ไม่แน่ใจว่าอันนี้จะแก้ไขในกรณีใด ๆ แต่จะแก้ไขในสภาพแวดล้อมของฉัน ฉันแค่ไม่ได้ใช้ WaitForExit และใช้ WaitHandle.WaitAll ทั้งสัญญาณเอาต์พุตและข้อผิดพลาดสิ้นสุด ฉันจะดีใจถ้าใครจะเห็นปัญหาที่เป็นไปได้กับที่ หรือว่าจะช่วยใครซักคน สำหรับฉันมันจะดีกว่าเพราะไม่ใช้การหมดเวลา

private static int DoProcess(string workingDir, string fileName, string arguments)
{
    int exitCode;
    using (var process = new Process
    {
        StartInfo =
        {
            WorkingDirectory = workingDir,
            WindowStyle = ProcessWindowStyle.Hidden,
            CreateNoWindow = true,
            UseShellExecute = false,
            FileName = fileName,
            Arguments = arguments,
            RedirectStandardError = true,
            RedirectStandardOutput = true
        },
        EnableRaisingEvents = true
    })
    {
        using (var outputWaitHandle = new AutoResetEvent(false))
        using (var errorWaitHandle = new AutoResetEvent(false))
        {
            process.OutputDataReceived += (sender, args) =>
            {
                // ReSharper disable once AccessToDisposedClosure
                if (args.Data != null) Debug.Log(args.Data);
                else outputWaitHandle.Set();
            };
            process.ErrorDataReceived += (sender, args) =>
            {
                // ReSharper disable once AccessToDisposedClosure
                if (args.Data != null) Debug.LogError(args.Data);
                else errorWaitHandle.Set();
            };

            process.Start();
            process.BeginOutputReadLine();
            process.BeginErrorReadLine();

            WaitHandle.WaitAll(new WaitHandle[] { outputWaitHandle, errorWaitHandle });

            exitCode = process.ExitCode;
        }
    }
    return exitCode;
}

ฉันใช้สิ่งนี้และห่อด้วย Task.Run เพื่อจัดการการหมดเวลาฉันยังส่งคืนโพรเซสเพื่อฆ่าเวลานอก
plus5volt

0

ฉันคิดว่าด้วย async เป็นไปได้ที่จะมีทางออกที่หรูหรากว่าและไม่มีการหยุดชะงักแม้จะใช้ทั้งเอาท์พุทมาตรฐานและมาตรฐานข้อผิดพลาด:

using (Process process = new Process())
{
    process.StartInfo.FileName = filename;
    process.StartInfo.Arguments = arguments;
    process.StartInfo.UseShellExecute = false;
    process.StartInfo.RedirectStandardOutput = true;
    process.StartInfo.RedirectStandardError = true;

    process.Start();

    var tStandardOutput = process.StandardOutput.ReadToEndAsync();
    var tStandardError = process.StandardError.ReadToEndAsync();

    if (process.WaitForExit(timeout))
    {
        string output = await tStandardOutput;
        string errors = await tStandardError;

        // Process completed. Check process.ExitCode here.
    }
    else
    {
        // Timed out.
    }
}

มันมีพื้นฐานอยู่บนคำตอบของ Mark Byers หากคุณไม่ได้อยู่ในวิธีการซิงค์คุณสามารถใช้string output = tStandardOutput.result;แทนawait



-1

โพสต์นี้อาจจะล้าสมัย แต่ฉันค้นพบสาเหตุหลักที่ทำให้แฮงค์มักจะเกิดจากการโอเวอร์โฟลว์สแต็กสำหรับการเปลี่ยนเส้นทางมาตรฐานออกหรือถ้าคุณเปลี่ยนเส้นทางมาตรฐาน

เนื่องจากข้อมูลเอาต์พุตหรือข้อมูลข้อผิดพลาดมีขนาดใหญ่มันจะทำให้เวลาแฮงค์เนื่องจากยังคงประมวลผลเป็นระยะเวลาไม่ จำกัด

ดังนั้นเพื่อแก้ไขปัญหานี้:

p.StartInfo.RedirectStandardoutput = False
p.StartInfo.RedirectStandarderror = False

11
ปัญหาคือคนตั้งอย่างชัดเจนว่าเป็นจริงเพราะพวกเขาต้องการเข้าถึงสตรีมเหล่านั้น! แน่นอนเราสามารถปล่อยให้พวกเขาเป็นเท็จ
276648

-1

ให้เราเรียกรหัสตัวอย่างที่โพสต์ไว้ที่นี่ตัวเปลี่ยนเส้นทางและโปรแกรมอื่นที่เปลี่ยนเส้นทาง ถ้าเป็นฉันแล้วฉันอาจจะเขียนโปรแกรมเปลี่ยนเส้นทางการทดสอบที่สามารถใช้ในการทำซ้ำปัญหา

ดังนั้นฉันทำ สำหรับข้อมูลการทดสอบฉันใช้ ECMA-334 C # Language Specificationv PDF; ประมาณ 5MB ต่อไปนี้เป็นส่วนสำคัญของการที่

StreamReader stream = null;
try { stream = new StreamReader(Path); }
catch (Exception ex)
{
    Console.Error.WriteLine("Input open error: " + ex.Message);
    return;
}
Console.SetIn(stream);
int datasize = 0;
try
{
    string record = Console.ReadLine();
    while (record != null)
    {
        datasize += record.Length + 2;
        record = Console.ReadLine();
        Console.WriteLine(record);
    }
}
catch (Exception ex)
{
    Console.Error.WriteLine($"Error: {ex.Message}");
    return;
}

ค่าชุดข้อมูลไม่ตรงกับขนาดไฟล์จริง แต่ไม่สำคัญ ไม่ชัดเจนว่าไฟล์ PDF ใช้ทั้ง CR และ LF ที่ท้ายบรรทัดเสมอ แต่ไม่สำคัญสำหรับเรื่องนี้ คุณสามารถใช้ไฟล์ข้อความขนาดใหญ่อื่น ๆ เพื่อทดสอบได้

การใช้รหัสตัวเปลี่ยนเส้นทางตัวอย่างแฮงค์เมื่อฉันเขียนข้อมูลจำนวนมาก แต่ไม่ใช่เมื่อฉันเขียนจำนวนเล็กน้อย

ฉันพยายามอย่างมากที่จะติดตามการดำเนินการของรหัสนั้นและฉันไม่สามารถทำได้ ฉันแสดงความคิดเห็นในบรรทัดของโปรแกรมที่ถูกเปลี่ยนเส้นทางซึ่งปิดการใช้งานการสร้างคอนโซลสำหรับโปรแกรมที่ถูกเปลี่ยนเส้นทางเพื่อพยายามหาหน้าต่างคอนโซลแยกต่างหาก แต่ฉันทำไม่ได้

แล้วฉันได้พบวิธีการเริ่มต้นแอปคอนโซลในหน้าต่างใหม่หน้าต่างของผู้ปกครองหรือไม่มีหน้าต่าง เห็นได้ชัดว่าเราไม่สามารถแยกคอนโซลได้อย่างง่ายดายเมื่อโปรแกรมคอนโซลหนึ่งเริ่มโปรแกรมคอนโซลอื่นโดยไม่มี ShellExecute และเนื่องจาก ShellExecute ไม่รองรับการเปลี่ยนเส้นทางเราต้องใช้คอนโซลร่วมกันแม้ว่าเราจะไม่ระบุหน้าต่างสำหรับกระบวนการอื่น

ฉันสันนิษฐานว่าหากโปรแกรมที่ถูกเปลี่ยนเส้นทางเติมบัฟเฟอร์ในที่ใดที่หนึ่งมันจะต้องรอข้อมูลที่จะอ่านและหาก ณ จุดนั้นไม่มีข้อมูลถูกอ่านโดยผู้เปลี่ยนเส้นทางแล้วมันจะเป็นการหยุดชะงัก

วิธีแก้ไขคือไม่ใช้ ReadToEnd และอ่านข้อมูลในขณะที่กำลังเขียนข้อมูล แต่ไม่จำเป็นต้องใช้การอ่านแบบอะซิงโครนัส การแก้ปัญหานั้นค่อนข้างง่าย งานต่อไปนี้สำหรับฉันด้วย PDF ขนาด 5 MB

ProcessStartInfo info = new ProcessStartInfo(TheProgram);
info.CreateNoWindow = true;
info.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
info.RedirectStandardOutput = true;
info.UseShellExecute = false;
Process p = Process.Start(info);
string record = p.StandardOutput.ReadLine();
while (record != null)
{
    Console.WriteLine(record);
    record = p.StandardOutput.ReadLine();
}
p.WaitForExit();

ความเป็นไปได้อีกอย่างคือการใช้โปรแกรม GUI เพื่อทำการเปลี่ยนเส้นทาง โค้ดก่อนหน้านี้ทำงานในแอ็พพลิเคชัน WPF ยกเว้นด้วยการแก้ไขที่ชัดเจน


-3

ฉันมีปัญหาเดียวกัน แต่เหตุผลก็แตกต่างกัน อย่างไรก็ตามมันจะเกิดขึ้นภายใต้ Windows 8 แต่ไม่ใช่ใน Windows 7 บรรทัดต่อไปนี้น่าจะทำให้เกิดปัญหา

pProcess.StartInfo.UseShellExecute = False

การแก้ปัญหาคือการไม่ปิดการใช้งาน UseShellExecute ตอนนี้ฉันได้รับหน้าต่างป๊อปอัพของเชลล์ซึ่งไม่เป็นที่ต้องการ แต่ดีกว่าโปรแกรมที่รอโดยเฉพาะไม่มีอะไรเกิดขึ้น ดังนั้นฉันจึงเพิ่มการแก้ไขต่อไปนี้สำหรับสิ่งนั้น:

pProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden

ตอนนี้สิ่งเดียวที่รบกวนฉันคือทำไมสิ่งนี้จึงเกิดขึ้นภายใต้ Windows 8 ตั้งแต่แรก


1
คุณต้องUseShellExecuteตั้งค่าเป็นเท็จถ้าคุณต้องการเปลี่ยนเส้นทางผลลัพธ์
แบรดมัวร์
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.