บางครั้งกระบวนการจะหยุดทำงานในขณะที่รอ Exit


13

อะไรเป็นสาเหตุที่ทำให้กระบวนการของฉันหยุดชะงักในระหว่างรอทางออก

รหัสนี้จะต้องเริ่มสคริปต์ PowerShell ซึ่งภายในดำเนินการหลายอย่างเช่นเริ่มต้นการคอมไพล์โค้ดใหม่ผ่าน MSBuild แต่อาจเป็นปัญหาคือมันสร้างเอาต์พุตมากเกินไปและรหัสนี้จะติดค้างในขณะที่รอเพื่อออกจากสคริปต์ Power shell อย่างถูกต้อง

มันค่อนข้าง "แปลก" เพราะบางครั้งรหัสนี้ทำงานได้ดีและบางครั้งมันก็ติด

รหัสค้างที่:

process.WaitForExit (ProcessTimeOutMiliseconds);

สคริปต์ Powershell ดำเนินการในระยะเวลา 1-2 วินาทีในขณะที่การหมดเวลาคือ 19 วินาที

public static (bool Success, string Logs) ExecuteScript(string path, int ProcessTimeOutMiliseconds, params string[] args)
{
    StringBuilder output = new StringBuilder();
    StringBuilder error = new StringBuilder();

    using (var outputWaitHandle = new AutoResetEvent(false))
    using (var errorWaitHandle = new AutoResetEvent(false))
    {
        try
        {
            using (var process = new Process())
            {
                process.StartInfo = new ProcessStartInfo
                {
                    WindowStyle = ProcessWindowStyle.Hidden,
                    FileName = "powershell.exe",
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                    UseShellExecute = false,
                    Arguments = $"-ExecutionPolicy Bypass -File \"{path}\"",
                    WorkingDirectory = Path.GetDirectoryName(path)
                };

                if (args.Length > 0)
                {
                    var arguments = string.Join(" ", args.Select(x => $"\"{x}\""));
                    process.StartInfo.Arguments += $" {arguments}";
                }

                output.AppendLine($"args:'{process.StartInfo.Arguments}'");

                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();

                process.WaitForExit(ProcessTimeOutMiliseconds);

                var logs = output + Environment.NewLine + error;

                return process.ExitCode == 0 ? (true, logs) : (false, logs);
            }
        }
        finally
        {
            outputWaitHandle.WaitOne(ProcessTimeOutMiliseconds);
            errorWaitHandle.WaitOne(ProcessTimeOutMiliseconds);
        }
    }
}

สคริปต์:

start-process $args[0] App.csproj -Wait -NoNewWindow

[string]$sourceDirectory  = "\bin\Debug\*"
[int]$count = (dir $sourceDirectory | measure).Count;

If ($count -eq 0)
{
    exit 1;
}
Else
{
    exit 0;
}

ที่ไหน

$args[0] = "C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\MSBuild\Current\Bin\MSBuild.exe"

แก้ไข

วิธีการแก้ปัญหาของ @ ingen ฉันได้เพิ่ม wrapper ขนาดเล็กซึ่งพยายามที่จะดำเนินการแขวน MS Build

public static void ExecuteScriptRx(string path, int processTimeOutMilliseconds, out string logs, out bool success, params string[] args)
{
    var current = 0;
    int attempts_count = 5;
    bool _local_success = false;
    string _local_logs = "";

    while (attempts_count > 0 && _local_success == false)
    {
        Console.WriteLine($"Attempt: {++current}");
        InternalExecuteScript(path, processTimeOutMilliseconds, out _local_logs, out _local_success, args);
        attempts_count--;
    }

    success = _local_success;
    logs = _local_logs;
}

InternalExecuteScriptรหัสของไอเอ็นจีอยู่ที่ไหน


บรรทัดใดที่กระบวนการหยุดทำงานจริง และแนะนำรหัสของคุณให้มากขึ้น
Mr.AF

@ Mr.AF เรียบร้อยแล้ว
Joelty

1
การเรียก Powershell ที่แท้จริงคือสิ่งหนึ่ง แต่สิ่งที่คุณไม่ได้ให้คือส่วนที่เหลือของสคริปต์ที่คุณพยายามดำเนินการในขณะที่ภายใน Powershell การโทรด้วยตนเองไม่ได้เป็นปัญหา แต่อยู่ในสิ่งที่คุณพยายามจะทำ แก้ไขโพสต์ของคุณและใส่คำสั่งโทร / คำสั่งที่คุณพยายามเรียกใช้
DRapp

1
มันแปลกจริง ๆ ฉันพยายามทำซ้ำข้อผิดพลาด มันเกิดขึ้นแบบสุ่มสองครั้งใน 20 ครั้งหรือบางสิ่งและฉันไม่สามารถเรียกใช้อีกครั้งได้
KiKoS

1
@ แปลกที่น่าสนใจคุณพูดว่าRxวิธีการทำงาน (ในขณะที่มันไม่ได้หมดเวลา) แม้จะมีกระบวนการ MSBuild หลงทางอยู่รอบ ๆ นำไปสู่การรออย่างไม่มีกำหนด? สนใจที่จะทราบวิธีการจัดการ
Clint

คำตอบ:


9

เริ่มจากสรุปคำตอบที่ยอมรับในโพสต์ที่เกี่ยวข้องกัน

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

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

แม้จะเป็นคำตอบที่ได้รับการยอมรับก็ยังต้องดิ้นรนกับคำสั่งของการประหารชีวิตในบางกรณี

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

มันอยู่ในสถานการณ์แบบนี้ซึ่งคุณต้องการจัดทำเหตุการณ์หลาย ๆ เหตุการณ์ที่ Rx ส่องแสงจริงๆ

หมายเหตุ. NET. การใช้งาน Rx นั้นมีอยู่ในแพ็คเกจ System.Reactive NuGet

มาดำน้ำกันเพื่อดูว่า Rx ช่วยให้การทำงานกับกิจกรรมทำได้อย่างไร

// Subscribe to OutputData
Observable.FromEventPattern<DataReceivedEventArgs>(process, nameof(Process.OutputDataReceived))
    .Subscribe(
        eventPattern => output.AppendLine(eventPattern.EventArgs.Data),
        exception => error.AppendLine(exception.Message)
    ).DisposeWith(disposables);

FromEventPatternช่วยให้เราสามารถแมปเหตุการณ์ที่แตกต่างของเหตุการณ์ไปยังสตรีมแบบรวม (หรือที่สังเกตได้) สิ่งนี้ช่วยให้เราสามารถจัดการเหตุการณ์ในไปป์ไลน์ (ที่มีความหมายคล้าย LINQ) Subscribeเกินใช้ที่นี่มีให้กับและAction<EventPattern<...>> Action<Exception>เมื่อใดก็ตามที่เหตุการณ์ที่สังเกตจะเพิ่มขึ้นของมันsenderและargsจะถูกห่อโดยการผลักดันผ่านEventPattern Action<EventPattern<...>>เมื่อมีการยกข้อยกเว้นในไปป์ไลน์Action<Exception>จะใช้

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

ด้วย Rx เราจะได้รับIDisposableเมื่อเราทำการสมัครสมาชิก เมื่อเรากำจัดมันเราจะสิ้นสุดการสมัครสมาชิกอย่างมีประสิทธิภาพ ด้วยการเพิ่มDisposeWithวิธีการขยาย (ยืมมาจากRxUI ) เราสามารถเพิ่มหลายIDisposables ไปยัง a CompositeDisposable(ตั้งชื่อdisposablesในตัวอย่างโค้ด) disposables.Dispose()เมื่อเรากำลังทำทุกสิ่งที่เราสามารถจบการสมัครทั้งหมดกับหนึ่งในการเรียกร้องให้

เพื่อให้แน่ใจว่าไม่มีสิ่งใดที่เราสามารถทำได้กับ Rx ซึ่งเราไม่สามารถทำกับ vanilla .NET ได้ โค้ดที่ได้นั้นง่ายกว่ามากเมื่อคุณปรับให้เข้ากับวิธีการคิด

public static void ExecuteScriptRx(string path, int processTimeOutMilliseconds, out string logs, out bool success, params string[] args)
{
    StringBuilder output = new StringBuilder();
    StringBuilder error = new StringBuilder();

    using (var process = new Process())
    using (var disposables = new CompositeDisposable())
    {
        process.StartInfo = new ProcessStartInfo
        {
            WindowStyle = ProcessWindowStyle.Hidden,
            FileName = "powershell.exe",
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            UseShellExecute = false,
            Arguments = $"-ExecutionPolicy Bypass -File \"{path}\"",
            WorkingDirectory = Path.GetDirectoryName(path)
        };

        if (args.Length > 0)
        {
            var arguments = string.Join(" ", args.Select(x => $"\"{x}\""));
            process.StartInfo.Arguments += $" {arguments}";
        }

        output.AppendLine($"args:'{process.StartInfo.Arguments}'");

        // Raise the Process.Exited event when the process terminates.
        process.EnableRaisingEvents = true;

        // Subscribe to OutputData
        Observable.FromEventPattern<DataReceivedEventArgs>(process, nameof(Process.OutputDataReceived))
            .Subscribe(
                eventPattern => output.AppendLine(eventPattern.EventArgs.Data),
                exception => error.AppendLine(exception.Message)
            ).DisposeWith(disposables);

        // Subscribe to ErrorData
        Observable.FromEventPattern<DataReceivedEventArgs>(process, nameof(Process.ErrorDataReceived))
            .Subscribe(
                eventPattern => error.AppendLine(eventPattern.EventArgs.Data),
                exception => error.AppendLine(exception.Message)
            ).DisposeWith(disposables);

        var processExited =
            // Observable will tick when the process has gracefully exited.
            Observable.FromEventPattern<EventArgs>(process, nameof(Process.Exited))
                // First two lines to tick true when the process has gracefully exited and false when it has timed out.
                .Select(_ => true)
                .Timeout(TimeSpan.FromMilliseconds(processTimeOutMilliseconds), Observable.Return(false))
                // Force termination when the process timed out
                .Do(exitedSuccessfully => { if (!exitedSuccessfully) { try { process.Kill(); } catch {} } } );

        // Subscribe to the Process.Exited event.
        processExited
            .Subscribe()
            .DisposeWith(disposables);

        // Start process(ing)
        process.Start();

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

        // Wait for the process to terminate (gracefully or forced)
        processExited.Take(1).Wait();

        logs = output + Environment.NewLine + error;
        success = process.ExitCode == 0;
    }
}

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

Subscribeครั้งแรกเมื่อเราเปิดใช้งานได้โดยการเรียก และต่อมาเมื่อเราต้องการ 'รอ' ค่าแรก

var processExited =
    // Observable will tick when the process has gracefully exited.
    Observable.FromEventPattern<EventArgs>(process, nameof(Process.Exited))
        // First two lines to tick true when the process has gracefully exited and false when it has timed out.
        .Select(_ => true)
        .Timeout(TimeSpan.FromMilliseconds(processTimeOutMilliseconds), Observable.Return(false))
        // Force termination when the process timed out
        .Do(exitedSuccessfully => { if (!exitedSuccessfully) { try { process.Kill(); } catch {} } } );

// Subscribe to the Process.Exited event.
processExited
    .Subscribe()
    .DisposeWith(disposables);

// Start process(ing)
...

// Wait for the process to terminate (gracefully or forced)
processExited.Take(1).Wait();

หนึ่งในปัญหาของ OP คือการสันนิษฐานว่าprocess.WaitForExit(processTimeOutMiliseconds)จะยุติกระบวนการเมื่อหมดเวลา จากMSDN :

สั่งให้ส่วนประกอบกระบวนการรอจำนวนมิลลิวินาทีที่ระบุเพื่อให้กระบวนการที่เกี่ยวข้องจบการทำงาน

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

รหัสอธิบายได้ด้วยตนเอง หากexitedSuccessfullyกระบวนการนี้ได้ยุติลงอย่างงดงาม ถ้าไม่exitedSuccessfullyยกเลิกจะต้องมีการบังคับ โปรดทราบว่าprocess.Kill()จะดำเนินการถ่ายทอดสดอ้างอิงคำพูด อย่างไรก็ตามการโทรprocess.WaitForExit()ทันทีหลังจากนั้นจะเปิดโอกาสให้เกิดการชะงักงันอีกครั้ง ดังนั้นแม้ในกรณีที่มีการยกเลิกบังคับจะเป็นการดีกว่าที่จะปล่อยให้การกำจัดทิ้งทั้งหมดหมดไปเมื่อusingขอบเขตสิ้นสุดลงเนื่องจากเอาต์พุตสามารถถูกพิจารณาว่าถูกขัดจังหวะ / เสียหาย

สิ่งtry catchก่อสร้างนั้นสงวนไว้สำหรับกรณีพิเศษ (ไม่มีการเล่นสำนวน) ที่คุณได้ปรับให้สอดคล้องprocessTimeOutMillisecondsกับเวลาจริงที่กระบวนการต้องการทำให้เสร็จ กล่าวอีกนัยหนึ่งสภาพการแข่งขันจะเกิดขึ้นระหว่างProcess.Exitedเหตุการณ์และตัวจับเวลา process.Kill()ความเป็นไปได้ที่เกิดขึ้นนี้จะขยายอีกครั้งโดยธรรมชาติที่ไม่ตรงกันของ ฉันพบมันครั้งเดียวในระหว่างการทดสอบ


เพื่อความสมบูรณ์DisposeWithวิธีการขยาย

/// <summary>
/// Extension methods associated with the IDisposable interface.
/// </summary>
public static class DisposableExtensions
{
    /// <summary>
    /// Ensures the provided disposable is disposed with the specified <see cref="CompositeDisposable"/>.
    /// </summary>
    public static T DisposeWith<T>(this T item, CompositeDisposable compositeDisposable)
        where T : IDisposable
    {
        if (compositeDisposable == null)
        {
            throw new ArgumentNullException(nameof(compositeDisposable));
        }

        compositeDisposable.Add(item);
        return item;
    }
}

4
IMHO คุ้มค่าเงินแน่นอน คำตอบที่ดีและคำแนะนำดี ๆ ในหัวข้อ RX
quetzalcoatl

ขอบคุณ !!! ExecuteScriptRxจัดการของคุณhangsอย่างสมบูรณ์ แฮงค์ยังคงโชคร้ายเกิดขึ้น แต่ฉันเพิ่งเพิ่ม wrapper เล็ก ๆ ของคุณExecuteScriptRxที่มีประสิทธิภาพRetryแล้วมันจะทำงานได้ดี เหตุผลที่ MSBUILD แฮงค์อาจเป็น @Clint answer PS: รหัสนั้นทำให้ฉันรู้สึกโง่ <lol> นั่นเป็นครั้งแรกที่ฉันเห็นSystem.Reactive.Linq;
Joelty

รหัสของ Wrapper ในโพสต์หลัก
Joelty

3

เพื่อประโยชน์ของผู้อ่านฉันจะแบ่งมันเป็น 2 ส่วน

ส่วนที่ A: ปัญหา & วิธีจัดการกับสถานการณ์ที่คล้ายกัน

ส่วน B: การแก้ปัญหาและการแก้ปัญหา

ส่วนที่ A: ปัญหา

เมื่อปัญหานี้เกิดขึ้น - กระบวนการปรากฏขึ้นในตัวจัดการงานหลังจากนั้น 2-3 วินาทีจะหายไป (ไม่เป็นไร) จากนั้นรอให้หมดเวลาและมีข้อยกเว้นเกิดขึ้นกับระบบ SystemInInvalidOperationException

& ดูสถานการณ์ 4 ด้านล่าง

ในรหัสของคุณ:

  1. Process.WaitForExit(ProcessTimeOutMiliseconds); ด้วยสิ่งนี้คุณกำลังรอProcessการหมดเวลาหรือออกที่เคยเกิดขึ้นครั้งแรก
  2. OutputWaitHandle.WaitOne(ProcessTimeOutMiliseconds)และerrorWaitHandle.WaitOne(ProcessTimeOutMiliseconds); ด้วยสิ่งนี้คุณกำลังรอOutputData & ErrorDataสตรีมการดำเนินการอ่านเพื่อให้สัญญาณเสร็จสมบูรณ์
  3. Process.ExitCode == 0 รับสถานะของกระบวนการเมื่อออก

การตั้งค่าที่แตกต่างกันและคำเตือน:

  • สถานการณ์ 1 (เส้นทางที่มีความสุข) : กระบวนการเสร็จสิ้นก่อนหมดเวลาและทำให้ stdoutput และ stderror ของคุณเสร็จสิ้นก่อนที่จะหมด
  • สถานการณ์ที่ 2 : กระบวนการ OutputWaitHandle & ErrorWaitHandle หมดเวลาอย่างไรก็ตาม stdoutput & stderror ยังคงถูกอ่านและเสร็จสิ้นหลังจาก WaitHandlers หมดเวลา สิ่งนี้นำไปสู่ข้อยกเว้นอื่นObjectDisposedException()
  • สถานการณ์ 3 : กระบวนการหมดเวลาใช้งานครั้งแรก (19 วินาที) แต่ stdout และ stderror ทำงานอยู่คุณรอให้ WaitHandler's หมดเวลา (19 วินาที) ทำให้เพิ่มความล่าช้า + 19 วินาที
  • สถานการณ์ที่ 4 : Process ครั้งจากความพยายามและรหัสแบบสอบถามก่อนเวลาอันควรทำให้เกิดข้อผิดพลาดProcess.ExitCodeSystem.InvalidOperationException: Process must exit before requested information can be determined

ฉันได้ทดสอบสถานการณ์นี้มาหลายสิบครั้งและทำงานได้ดีการตั้งค่าต่อไปนี้ถูกนำไปใช้ขณะทำการทดสอบ

  • ขนาดของกระแสเอาท์พุทตั้งแต่ 5KB ถึง 198KB โดยเริ่มต้นสร้างประมาณ 2-15 โครงการ
  • หมดเวลาก่อนกำหนด & กระบวนการออกภายในหน้าต่างหมดเวลา


ปรับปรุงรหัส

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

    //First waiting for ReadOperations to Timeout and then check Process to Timeout
    if (!outputWaitHandle.WaitOne(ProcessTimeOutMiliseconds) && !errorWaitHandle.WaitOne(ProcessTimeOutMiliseconds)
        && !process.WaitForExit(ProcessTimeOutMiliseconds)  )
    {
        //To cancel the Read operation if the process is stil reading after the timeout this will prevent ObjectDisposeException
        process.CancelOutputRead();
        process.CancelErrorRead();

        Console.ForegroundColor = ConsoleColor.Red;
        Console.WriteLine("Timed Out");
        Logs = output + Environment.NewLine + error;
       //To release allocated resource for the Process
        process.Close();
        return  (false, logs);
    }

    Console.ForegroundColor = ConsoleColor.Green;
    Console.WriteLine("Completed On Time");
    Logs = output + Environment.NewLine + error;
    ExitCode = process.ExitCode.ToString();
    // Close frees the memory allocated to the exited process
    process.Close();

    //ExitCode now accessible
    return process.ExitCode == 0 ? (true, logs) : (false, logs);
    }
}
finally{}

แก้ไข:

หลังจากเล่นไปหลายชั่วโมงกับ MSBuild ในที่สุดฉันก็สามารถทำให้เกิดปัญหาขึ้นที่ระบบของฉัน


ส่วน B: ปัญหาสันทนาการและการแก้ไข

MSBuildมี-m[:number]สวิตช์ที่ใช้เพื่อระบุจำนวนสูงสุดของกระบวนการที่เกิดขึ้นพร้อมกันที่จะใช้เมื่อสร้าง

เมื่อเปิดใช้งานสิ่งนี้ MSBuild จะวางไข่เป็นจำนวนโหนดที่ยังคงอยู่แม้ว่าจะสร้างเสร็จสมบูรณ์ก็ตาม ตอนนี้ Process.WaitForExit(milliseconds)จะรอไม่เคยออกและหมดเวลาในที่สุด

ฉันสามารถแก้ไขปัญหานี้ได้สองวิธี

  • วางกระบวนการ MSBuild ทางอ้อมผ่าน CMD

    $path1 = """C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\MSBuild.exe"" ""C:\Users\John\source\repos\Test\Test.sln"" -maxcpucount:3"
    $cmdOutput = cmd.exe /c $path1  '2>&1'
    $cmdOutput
  • ใช้ MSBuild ต่อไป แต่ให้แน่ใจว่าตั้ง nodeReuse เป็น False

    $filepath = "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\MSBuild.exe"
    $arg1 = "C:\Users\John\source\repos\Test\Test.sln"
    $arg2 = "-m:3"
    $arg3 = "-nr:False"
    
    Start-Process -FilePath $filepath -ArgumentList $arg1,$arg2,$arg3 -Wait -NoNewWindow
  • แม้ว่าจะไม่ได้เปิดใช้งานการสร้างแบบขนานคุณยังสามารถป้องกันไม่ให้กระบวนการของคุณค้างอยู่ได้WaitForExitด้วยการเปิดตัว Build ผ่านCMDดังนั้นคุณจึงไม่ต้องสร้างการพึ่งพาโดยตรงกับกระบวนการสร้าง

    $path1 = """C:\....\15.0\Bin\MSBuild.exe"" ""C:\Users\John\source\Test.sln"""
    $cmdOutput = cmd.exe /c $path1  '2>&1'
    $cmdOutput

วิธีที่สองเป็นที่ต้องการเนื่องจากคุณไม่ต้องการให้โหนด MSBuild มากเกินไปวางทับ


ดังนั้นดังที่ฉันได้กล่าวไว้ข้างต้นขอบคุณ"-nr:False","-m:3"ดูเหมือนว่าจะมีการแก้ไขพฤติกรรม MSBuild hang-ish ซึ่งRx solutionทำให้กระบวนการทั้งหมดค่อนข้างน่าเชื่อถือ (แสดงเวลา) ฉันหวังว่าฉันจะยอมรับคำตอบทั้งสองหรือให้สองรางวัล
Joelty

@Joelty ผมก็แค่พยายามที่จะทราบว่าRxวิธีการในการแก้ปัญหาอื่น ๆ -nr:False" ,"-m:3"สามารถที่จะแก้ปัญหานี้ได้โดยไม่ต้องใช้ ในความเข้าใจของฉันมันจัดการการรออย่างไม่หยุดหย่อนจากการหยุดชะงักและสิ่งอื่น ๆ ที่ฉันได้กล่าวถึงในส่วนที่ 1 และสาเหตุที่เกิดขึ้นในส่วนที่ 2 คือสิ่งที่ฉันเชื่อว่าเป็นสาเหตุของปัญหาที่คุณเผชิญ;) ฉันอาจผิด ฉันถามเวลาเท่านั้นที่จะบอก ... ไชโย !!
Clint

3

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

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

public async Task<int> RunProcessAsync(params string[] args)
    {
        try
        {
            var tcs = new TaskCompletionSource<int>();

            var process = new Process
            {
                StartInfo = {
                    FileName = 'file path',
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                    Arguments = "shell command",
                    UseShellExecute = false,
                    CreateNoWindow = true
                },
                EnableRaisingEvents = true
            };


            process.Exited += (sender, args) =>
            {
                tcs.SetResult(process.ExitCode);
                process.Dispose();
            };

            process.Start();
            // Use asynchronous read operations on at least one of the streams.
            // Reading both streams synchronously would generate another deadlock.
            process.BeginOutputReadLine();
            string tmpErrorOut = await process.StandardError.ReadToEndAsync();
            //process.WaitForExit();


            return await tcs.Task;
        }
        catch (Exception ee) {
            Console.WriteLine(ee.Message);
        }
        return -1;
    }

โค้ดด้านบนเป็นการต่อสู้ทดสอบการเรียก FFMPEG.exe ด้วยอาร์กิวเมนต์บรรทัดคำสั่ง ฉันกำลังแปลงไฟล์ mp4 เป็นไฟล์ mp3 และทำวิดีโอมากกว่า 1,000 รายการในแต่ละครั้งโดยไม่ล้มเหลว น่าเสียดายที่ฉันไม่ได้สัมผัสกับพลังโดยตรง แต่หวังว่ามันจะช่วยได้


มันแปลกรหัสนี้คล้ายกับวิธีอื่น ๆ ที่ล้มเหลว (ติดอยู่) ในความพยายามครั้งแรกและดูเหมือนจะทำงานได้ดี (เช่น 5 ครั้งอื่น ๆ ฉันจะทดสอบเพิ่มเติม) Btw ทำไมคุณถึงเล่นBegingOutputReadlineแล้วก็แสดงReadToEndAsyncต่อไปStandardError?
Joelty

OP กำลังอ่านแบบอะซิงโครนัสดังนั้นจึงไม่น่าเป็นไปได้ว่าการหยุดชะงักในบัฟเฟอร์ของคอนโซลเป็นปัญหาที่นี่
yaakov

0

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

หน้าเอกสารอยู่ที่นี่ ข้อความที่เกี่ยวข้อง:

เมื่อเอาต์พุตมาตรฐานถูกเปลี่ยนเส้นทางไปยังตัวจัดการเหตุการณ์แบบอะซิงโครนัสเป็นไปได้ว่าการประมวลผลเอาต์พุตจะไม่เสร็จสมบูรณ์เมื่อเมธอดนี้ส่งคืน เพื่อให้แน่ใจว่าการจัดการเหตุการณ์แบบอะซิงโครนัสเสร็จสมบูรณ์แล้วให้เรียกใช้ WaitForExit () โอเวอร์โหลดที่ไม่มีพารามิเตอร์หลังจากได้รับจริงจากการโอเวอร์โหลดนี้ เพื่อช่วยให้แน่ใจว่าเหตุการณ์ Exited ได้รับการจัดการอย่างถูกต้องในแอปพลิเคชัน Windows Forms ให้ตั้งค่าคุณสมบัติ SynchronizingObject

การแก้ไขโค้ดอาจมีลักษณะเช่นนี้:

if (process.WaitForExit(ProcessTimeOutMiliseconds))
{
  process.WaitForExit();
}

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