从另一个线程更新 UI 只工作一次
Update UI from another thread work only one time
我知道在 Whosebug 上有很多这方面的信息,但没有找到任何解决我问题的信息。
我制作了一个程序来使用 ffmpeg 处理一些视频文件。这个过程可能需要几分钟,所以,我正在尝试在另一个表单上制作进度条。
基本上,当我单击主窗体 (FormSync
) 上的按钮时,会显示一个新窗体。这个表单只有一个进度条和一个取消按钮(让我们调用 FormProgress
)。
为了执行 ffmpeg,我使用另一个 class (VideoProcessing
) 创建一个新进程,执行 ffmpeg,并监视 stderror(ffmpeg show progress on stderror)。每次 ffmpeg 显示进度时,此 class 解析输出、计算进度并引发事件 (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);
}
}
}
}
表格进度:
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 执行和捕获过程使用以下代码:
当我尝试执行代码时出现问题。
当我点击 que 按钮时,显示 FormProgress,但在此之后,显示进度条 "freeze"。程序运行良好,没有挂起,但进度条没有更新。
如果,在FormSync
,在InvokeMethod
,我用下面的内容替换原来的代码,我可以看到ffmpeg在工作,我的事件也在工作:
videoProcessing.OnMergeProgress += (seconds, currentSeconds) =>
{
Debug.WriteLine($"{currentSeconds}/{seconds}");
}
所以,问题不是 ffmpeg 或我的视频 class,而是 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}");
}
_formProgress.ShowDialog(this);
错误位于此处。在 window 关闭之前,ShowDialog() 不会 return。不清楚何时发生,但与错误无关。由于它不 return,Invoke() 调用会死锁且无法完成。这反过来会导致工作线程挂起。
部分问题是代码使用了 Invoke() 而不是 Begininvoke(),如果使用后一种方法,您就不会注意到相同的错误。并不是说这很漂亮,但它会隐藏问题。请注意,您不需要 Invoke(),不需要 return 值并且 BeginInvoke() 工作得很好。
您遇到此错误的最终原因是您需要初始化 ProgressBar.Maximum 属性。只是不要这样做,100 是一个很好的最大值。只需要一点数学知识,现在的进度是 (100 * currentSeconds) / 秒。
还需要调用ShowDialog(),有点尴尬,可以用Load事件调用MergeFiles()。
我知道在 Whosebug 上有很多这方面的信息,但没有找到任何解决我问题的信息。
我制作了一个程序来使用 ffmpeg 处理一些视频文件。这个过程可能需要几分钟,所以,我正在尝试在另一个表单上制作进度条。
基本上,当我单击主窗体 (FormSync
) 上的按钮时,会显示一个新窗体。这个表单只有一个进度条和一个取消按钮(让我们调用 FormProgress
)。
为了执行 ffmpeg,我使用另一个 class (VideoProcessing
) 创建一个新进程,执行 ffmpeg,并监视 stderror(ffmpeg show progress on stderror)。每次 ffmpeg 显示进度时,此 class 解析输出、计算进度并引发事件 (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);
}
}
}
}
表格进度:
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 执行和捕获过程使用以下代码:
当我尝试执行代码时出现问题。
当我点击 que 按钮时,显示 FormProgress,但在此之后,显示进度条 "freeze"。程序运行良好,没有挂起,但进度条没有更新。
如果,在FormSync
,在InvokeMethod
,我用下面的内容替换原来的代码,我可以看到ffmpeg在工作,我的事件也在工作:
videoProcessing.OnMergeProgress += (seconds, currentSeconds) =>
{
Debug.WriteLine($"{currentSeconds}/{seconds}");
}
所以,问题不是 ffmpeg 或我的视频 class,而是 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}");
}
_formProgress.ShowDialog(this);
错误位于此处。在 window 关闭之前,ShowDialog() 不会 return。不清楚何时发生,但与错误无关。由于它不 return,Invoke() 调用会死锁且无法完成。这反过来会导致工作线程挂起。
部分问题是代码使用了 Invoke() 而不是 Begininvoke(),如果使用后一种方法,您就不会注意到相同的错误。并不是说这很漂亮,但它会隐藏问题。请注意,您不需要 Invoke(),不需要 return 值并且 BeginInvoke() 工作得很好。
您遇到此错误的最终原因是您需要初始化 ProgressBar.Maximum 属性。只是不要这样做,100 是一个很好的最大值。只需要一点数学知识,现在的进度是 (100 * currentSeconds) / 秒。
还需要调用ShowDialog(),有点尴尬,可以用Load事件调用MergeFiles()。