从另一个线程更新UI仅工作一次

时间:2017-03-24 13:32:05

标签: c# multithreading

我知道有很多关于stackoverflow的信息,但是没有找到解决我问题的方法。

我制作了一个程序,将ffmpeg用于一些视频文件。这个过程可能需要几分钟,因此,我试图在另一个表单上创建一个进度条。

基本上,当我点击主表单(FormSync)上的按钮时,会显示一个新表单。此表单只有一个进度条和一个取消按钮(我们可以调用FormProgress)。

要执行ffmpeg,我使用另一个类(VideoProcessing)来创建一个新进程,执行ffmpeg,并监视stderror(ffmpeg显示stderror的进度)。每次ffmpeg显示进度时,此类将解析输出,计算进度并引发事件(OnMergeProgress)。

基本上,这是代码:

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

视频处理

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

基本上,FFMPEG执行和捕获过程使用以下代码: C# execute external program and capture (stream) the output

当我尝试执行代码时会出现问题。

当我点击que按钮时,会显示FormProgress,但在此之后,进度条&#34;冻结&#34;。该程序运行良好,没有挂起,但没有进度条更新。

如果在FormSyncInvokeMethod,我用以下内容替换原始代码,我可以看到ffmpeg正在运行,我的活动也在运作:

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

所以,问题不是ffmpeg或我的视频类,而是更新UI的东西。

如果我再次更改Invoke,但这次使用Debug,如下面的代码,Debug仅打印第一次更新,仅此而已:

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}");
}

1 个答案:

答案 0 :(得分:1)

  _formProgress.ShowDialog(this);

错误位于此处。在窗口关闭之前,ShowDialog()不会返回。不清楚何时发生,但与bug无关。由于它没有返回,Invoke()调用死锁并无法完成。这反过来导致工作线程挂起。

部分问题是代码使用Invoke()而不是Begininvoke(),如果你使用后一种方法,你就不会注意到同样的错误。并不是说这很漂亮,但它会掩盖这个问题。请注意,您不需要Invoke(),您不需要返回值,并且BeginInvoke()可以正常工作。

您遇到此错误的最终原因是您需要初始化ProgressBar.Maximum属性。不要这样做,100是一个很好的最大值。只需要一点点数学,现在进度(100 * currentSeconds)/秒。

你仍然需要调用ShowDialog(),这有点尴尬,你可以使用Load事件来调用MergeFiles()。