0

I am trying to pass a file path to the Process in a for loop and let ffmpeg.exe read only one video file's format each time. However, it stops after the textbox's text contains the first video's content. I have tried process.close(), process.hasExited, etc, but those couldn't stop the program from starting more than one Process and caused the delegate massively updated the textbox (the outputs weren't listed in the right order and I couldn't tell which lines belong to which video, and a few outputs were missing or showing twice in the text). How to execute only one process each time before the next in a for loop?

            foreach (String file in inputList)
            {
                if (flag == true)
                {
                    flag = false;
                    //textBox1.Text += file + Environment.NewLine;
                    checkvideoFormat(file);
                }

            }
            
            

        }
        catch (Exception err)
        {
            MessageBox.Show(err.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }
    private void checkvideoFormat(String filePath)
    {
        Process process1 = new Process();
        process1.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
        process1.StartInfo.CreateNoWindow = true;
        process1.StartInfo.UseShellExecute = false;
        process1.StartInfo.FileName = ".\\ffmpeg.exe";
        process1.StartInfo.WorkingDirectory = ".\\";
        process1.EnableRaisingEvents = true;
        process1.StartInfo.RedirectStandardOutput = true; //if this is true, UseShellExecute must be false. true if output should be written to StandardOutput
        process1.StartInfo.RedirectStandardError = true;
        //indicates that the associated Process has written a line that's terminated with a newline
        process1.ErrorDataReceived += new DataReceivedEventHandler(inputHandler);
        
        process1.Exited += (ending, p) =>
        {
            flag = true;
            Console.WriteLine(flag);
            //textBox1.Text += "Hey " + file + Environment.NewLine;
            //process1.CancelOutputRead();
            //process1.CancelErrorRead();//
        };
        
        process1.StartInfo.Arguments = "-i " + " \"" + filePath + "\"";
        Console.WriteLine(process1.StartInfo.Arguments);
        process1.Start();
        process1.BeginOutputReadLine();
        process1.BeginErrorReadLine();
        //process1.Close();
        //process1.WaitForExit();
        /*
        if(process1.HasExited == true)
        {
            //process1.Refresh();
            flag = true;
            //process1.Close();
            //process1.Kill();
            Thread.Sleep(1000);
        }
        */
    }



    int test_123 = 0;
    private void inputHandler(object sender, DataReceivedEventArgs l)
    {
        cba.Append(test_123 + l.Data + "\n");
        videoInput = test_123 + l.Data + Environment.NewLine;
        //Console.WriteLine(cba);
        //Process p = sender as Process;
        Console.WriteLine(videoInput);
        
        this.Invoke(new MethodInvoker(() =>
        {

            if (!String.IsNullOrEmpty(videoInput))
            {
                textBox1.Text += videoInput;
            }

        }));

        test_123++;

    }
Rand Random
  • 7,300
  • 10
  • 40
  • 88
TSLee
  • 33
  • 6
  • Why is `WaitForExit` commented out? It should do exactly what you want. – Fildor May 30 '23 at 09:12
  • @Fildor I did try WaitForExit and had the same effect as process.close(). – TSLee May 30 '23 at 09:16
  • I would do a recursive function to loop through the list. Add a callback to `checkvideoFormat` that will be called when process1 as returned a thing. Also, I used to have issues with ffmpeg too. Its process is not killed when it ends. You have to kill it youself. – Bibimission May 30 '23 at 09:26
  • 1
    @Bibimission what benefit are you expecting from _recursion_ ? – Fildor May 30 '23 at 09:26
  • @Fildor Plus WaitForExit will hold the program and even the computer. I don't want that happened. – TSLee May 30 '23 at 09:28
  • @Fildor Then you can wait for the end of a process to launch the second. There is other ways to do that but I like this one – Bibimission May 30 '23 at 09:29
  • _"Plus WaitForExit will hold the program **and even the computer**"_ - what are you talking about? _How_ would that "hold the computer"? – Fildor May 30 '23 at 09:31
  • What type of program is this? It is tagged as winforms, but you are using a bunch of Console.WriteLine. – JonasH May 30 '23 at 09:34
  • Ok, so: What you probably want to do is start the process and wait for exit an a separate thread (maybe using Task/await to outsource to a Pool Thread), so your UI Thread isn't blocked. If you use async/Task for that, you can just iterate the list. – Fildor May 30 '23 at 09:34
  • @Fildor Could you please give me an example of that? I am still new to C# and things like Delegate. – TSLee May 30 '23 at 09:40
  • @JonaH It's a windows forms app (.net framework) in Visual Studio. – TSLee May 30 '23 at 09:44
  • @TSLee In the comments to [JonasH's answer](https://stackoverflow.com/a/76363552/982149) I added some references for you to gain further info (generally and especially about his answer). – Fildor May 30 '23 at 10:11

2 Answers2

2

I would suggest rewriting your program to use an asynchronous pattern. In .Net 5+ you can use WaitForExitAsync

private async Task CheckvideoFormat(string filePath)
{
   using var process1 = new Process();

   ...

    return await process1.WaitForExitAsync() ; // make sure process is not disposed before the task has completed.
}

and make sure to await this call in your loop. This pattern should avoid blocking the UI thread.

For older versions you could probably simulate the behavior using the exited event and a TaskCompletionSource

JonasH
  • 28,608
  • 2
  • 10
  • 23
  • Thank you, I need to spend some time studying this. What are TaskCompletionSource and setResult doing? – TSLee May 30 '23 at 09:59
  • 1
    _"What are TaskCompletionSource and setResult doing?"_ - It's basically an adapter between two concepts of async programming. Doing the same thing solely with Event-Based-Async would be more complicated and unreadable. Using Task-Based-Async is very convenient but not natively supported by `Process` in your framework version. @TSLee Have a look into this: [Asynchronous programming patterns](https://learn.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/) – Fildor May 30 '23 at 10:02
  • 1
    ^^ and especially [Tasks and the Event-based Asynchronous Pattern (EAP)](https://learn.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/interop-with-other-asynchronous-patterns-and-types#tasks-and-the-event-based-asynchronous-pattern-eap) – Fildor May 30 '23 at 10:08
  • And a late Nitpick that's irrelevant to the answer: better follow naming conventions and make it `CheckVideoFormat` – Fildor May 30 '23 at 12:22
  • 1
    I am not sure that completing a `TaskCompletionSource` in the `Exited` event is robust. The process might terminate before attaching the handler. Have you considered the [`Process.WaitForExitAsync`](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.process.waitforexitasync) method? – Theodor Zoulias May 30 '23 at 15:23
  • 1
    @TheodorZoulias Would not attaching the handler before starting the process solve that problem? But yes, WaitForExitAsync would clearly be better if it is available. – JonasH May 31 '23 at 07:41
  • The `Process.Exited` event is tricky. If you want check out [this](https://stackoverflow.com/questions/10788982/is-there-any-async-equivalent-of-process-start) question, or [this](https://stackoverflow.com/questions/470256/process-waitforexit-asynchronously). – Theodor Zoulias May 31 '23 at 13:50
0

I've tried writing the code using the suggestion from @JonasH using asynchronous pattern. Furthermore I've tried letting all the actual work run in parallel and then only writing to the textbox when it's all done - thereby making sure it's in the right order. I'm unsure if there are any unfourtunate side effects by doing this. Also I'm unsure what you're trying to write for each file, but that should be fairly easy to correct.

Hope it is of use

    private async void Button_Click(object sender, RoutedEventArgs e)
    {
            string[] inputList = { "fileName1", "fileName2" };

        try
        {
            int fileNumber = 0;

            var output = await Task.WhenAll(inputList.Select(x => checkvideoFormat(x, fileNumber++)));

            foreach (var outputLine in output.Where(x => x != null))
            {
                textBox1.Text += outputLine + Environment.NewLine;
            }

        }
        catch (Exception err)
        {
            MessageBox.Show(err.Message, "Error", MessageBoxButton.OK);
        }
    }

    private async Task<string?> checkvideoFormat(string filePath, int fileNumber)
    {
        string? backText = null;
        Process process1 = new Process();
        process1.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
        process1.StartInfo.CreateNoWindow = true;
        process1.StartInfo.UseShellExecute = false;
        process1.StartInfo.FileName = ".\\ffmpeg.exe";
        process1.StartInfo.WorkingDirectory = ".\\";
        process1.EnableRaisingEvents = true;
        process1.StartInfo.RedirectStandardOutput = true; //if this is true, UseShellExecute must be false. true if output should be written to StandardOutput
        process1.StartInfo.RedirectStandardError = true;
        //indicates that the associated Process has written a line that's terminated with a newline
        process1.ErrorDataReceived += (sender, args) =>
        {
            backText = $"{fileNumber} {args.Data}";
        };

        process1.Exited += (ending, p) =>
        {
            // Don't know if you want to add something to backText here?
        };

        process1.StartInfo.Arguments = "-i " + " \"" + filePath + "\"";
        Console.WriteLine(process1.StartInfo.Arguments);
        process1.Start();
        await process1.WaitForExitAsync();

        return backText;
    }
Ykok
  • 1,313
  • 13
  • 15