0

I know there's a lot of information of this on stackoverflow, but don't find anything that resolved my problem.

I made a program to use ffmpeg with some video files. This process can take several minutes, so, I'm trying to make a progress bar on another form.

Basically, when I click on a button on my main form (FormSync), a new form is showed. This form have only a progressbar and a cancel button (lets call FormProgress).

To execute the ffmpeg, I use another class ( VideoProcessing) to create a new process, execute ffmpeg, and monitor the stderror (ffmpeg show progress on stderror). Every time ffmpeg show a progress, this class parse the output, calculate the progress, and raise a event (OnMergeProgress).

Basically, this is the code:

FormSync:

public partial class FormSync : Form 
{
  // this form show the progress of job
  private FormProgress _formProgress;

  // start the ffmpeg when click on button
  private void mergeButton_click(object sender, EventArgs e)
  {
    var files = new List<string>() {"file1.mp4", "file2.mp4"};
    MergeFiles(files);
  }

  // join all video files on a single file (using ffmpeg)
  private void MergeFiles(IEnumerable<string> videoFiles)
  {
    // instantiate the class that execute ffmpeg
    VideoProcessing videoProcessing = new VideoProcessing();

    // this class has a a event to show current progress
    // seconds = total seconds of video (sum length of all video files)
    // currentSeconds = current progress
    videoProcessing.OnMergeProgress += (seconds, currentSeconds) =>
    {
      Invoke((MethodInvoker) delegate()
      {
        // Instantiate the form of progress if not visible
        if (_formProgress = null)
        {
          // define the minimum and maximum value of progressbar on constructor
          _formProgress = new FormProgress(0, seconds);
          _formProgress.ShowDialog(this);
        }

        // update the progress bar value
        _formProgress.SetProgress(currentSeconds);
      }
    }
  }
}

FormProgress:

public partial class FormProgress : Form
{
  public FormProgress(int min, int max)
  {
    InitializeComponent();
    progressBar.Minimum = min;
    progressBar.Maximum = max;
  }

  public void SetProgress(int value)
  {
    value = (value <= progressBar.Minimum)
        ? progressBar.Minimum
        : (value >= progressBar.Maximum) ? progressBar.Maximum : value;

    progressBar.Value = value;
    Refresh();
  }
}

VideoProcessing:

internal class VideoProcessing
{
  // Events
  public delegate void MergeProgressHandler(int totalSeconds, int currentSeconds);
  public event MergeProgressHandler OnMergeProgress;

  private int _totalTimeVideos;

  public void MergeFiles(string[] videoFiles)
  {
    // calculate total time of all videos
    _totalTimeVideos = SomeFunctionToCalculateTotalTime();

    // create the Process object to execute FFMPEG (with stdout and stderr redirection)
    _process = CreateFFMPEGProcess(videoFiles);
  }

  // capture the stdout and stderr of ffmpeg
  private void MergeOutputHandler(object sendingProcess, DataReceivedEventArgs outline)
  {
    // capture the current progress
    // here will go a regex, and some other code to parse the info from ffmpeg

    // Raise the event
    OnMergeProgress?.Invoke(_totalTimeVideos, progressSeconds);
  }
}

Basically, the FFMPEG execution and capture process use the following code: C# execute external program and capture (stream) the output

The problem occur when I try to execute the code.

When I click que button, the FormProgress is showed, but after this, the progress bar "freeze". The program works good, no hangs here, but no update on progress bar.

If, in FormSync, on InvokeMethod, I replace the original code with the following content, I can see that ffmpeg is working, and my events are working too:

videoProcessing.OnMergeProgress += (seconds, currentSeconds) =>
{
  Debug.WriteLine($"{currentSeconds}/{seconds}");
}

So, the problem was not ffmpeg, or my video class, but something that update the UI.

If I change the Invoke again, but this time with the Debug, like code below, the Debug print only the first update, and nothing more:

videoProcessing.OnMergeProgress += (seconds, currentSeconds) =>
{
  Invoke((MethodInvoker) delegate() {
    if (_formProgress == null) {
      _formProgress = new FormProgress(Resources.merging_video_files, 0, seconds);
      _formProgress.ShowDialog(this);
    }

    _formProgress.SetProgress(currentSeconds);
  });
  Debug.WriteLine($"{currentSeconds}/{seconds}");
}
Community
  • 1
  • 1
Roberto Correia
  • 1,696
  • 5
  • 20
  • 36

1 Answers1

1
  _formProgress.ShowDialog(this);

The bug is located here. ShowDialog() does not return until the window is closed. Not clear when that happens, but not relevant to the bug. Since it does not return, the Invoke() call deadlocks and cannot complete. Which in turn causes the worker thread to hang.

Part of the problem is that the code uses Invoke() instead of Begininvoke(), you would not have noticed the same bug had you used the latter method. Not that this is pretty, but it would have hid the problem. Note that you don't need Invoke(), you don't need the return value and BeginInvoke() works just fine.

Ultimate reason you got this bug is because you needed to initialize the ProgressBar.Maximum property. Just don't do this, 100 is a good maximum value. Just a wee bit of math required, progress is now (100 * currentSeconds) / seconds.

You still need to call ShowDialog(), that's is a bit awkward, you can use the Load event to call MergeFiles().

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • only to do a quick test, I changed `ShowDialog`, to `Show`, and everything worked good. Now time to do a research about begininvoke! Tks! (the progressbar maximum value is initialized on form constructor) – Roberto Correia Mar 24 '17 at 14:17