【问题标题】:Update UI from another thread work only one time从另一个线程更新 UI 只工作一次
【发布时间】:2023-04-01 07:34:01
【问题描述】:

我知道stackoverflow上有很多这方面的信息,但没有找到任何解决我问题的方法。

我制作了一个程序来使用 ffmpeg 处理一些视频文件。这个过程可能需要几分钟,所以,我正在尝试在另一个表单上制作进度条。

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

为了执行ffmpeg,我使用另一个类(VideoProcessing)来创建一个新进程,执行ffmpeg,并监控stderror(ffmpeg show progress on 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,但在此之后,进度条会“冻结”。程序运行良好,此处没有挂起,但进度条没有更新。

如果,在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}");
}

【问题讨论】:

    标签: c# multithreading


    【解决方案1】:
      _formProgress.ShowDialog(this);
    

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

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

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

    还是需要调用ShowDialog(),有点别扭,可以使用Load事件调用MergeFiles()。

    【讨论】:

    • 只是为了做一个快速测试,我将ShowDialog更改为Show,一切正常。现在是时候对 begininvoke 进行研究了!谢! (进度条最大值在表单构造函数中初始化)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-30
    • 2014-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多