无法从使用 c# 在不同线程上运行的另一个页面 User.cs 更新 GUI (MainWindow.xaml) 中的进度条
Posted
技术标签:
【中文标题】无法从使用 c# 在不同线程上运行的另一个页面 User.cs 更新 GUI (MainWindow.xaml) 中的进度条【英文标题】:Not able to update progress bar in GUI (MainWindow.xaml) from another page User.cs running on a different thread using c# 【发布时间】:2020-08-05 08:05:18 【问题描述】:我有一个 GUI MainWindow.xaml 我有一个 ProgressBar 应该显示巨大的比较操作的进度,操作的逻辑写在 User.cs 页面。 基本上,MainWindow.xaml.cs 正在调用 User 类的方法 compare() 并进行比较并在最后显示输出。
我在比较过程中使用了忙碌指示器,它会挂起并在最后显示一条自定义消息。
MainWindow.xaml.cs
当我点击运行按钮时:
User compare = new User(Parser1, Parser2);
busyIndicator.IsBusy = true;
ThreadStart job = new ThreadStart(() =>
compare.compare();
//above line calls a method "compare" of User.cs class and does the comparison and returns back to this page
Dispatcher.Invoke(DispatcherPriority.Normal, new Action( () =>
busyIndicator.IsBusy = false;
MessageBoxResult result = Xceed.Wpf.Toolkit.MessageBox.Show("Finished", "Generation Finished", MessageBoxButton.OK);
) );
);
Thread thread = new Thread(job);
thread.Start();
User.cs
class User
public void compare()
for( i=0, i < 100, i++ )
int val = i;
那么我们如何在 GUI 的进度条中以完成百分比的形式显示compare.compare()
的进度。
我使用过BackGroundWorker但无法获取compare.compare()
的进度
谢谢,
【问题讨论】:
【参考方案1】:第 1 部分:线程
你不应该使用new Thread
:
Task.Run
将在线程池线程中运行作业,并将作业表示为Task
对象,您可以安全地使用await
,因为WPF 的调度程序支持Task
调度程序并且将始终恢复在await
之后的 UI 线程上。
请注意,尽管这意味着使用 async
和 await
关键字,但这段代码实际上并不是“真正的”异步代码(与网络套接字和文件系统上的真正异步 IO 不同)——它只是 .NET 表示一个并发执行的后台线程作为一个异步操作。
在 WPF(或 WinForms)上下文中使用 await
时,重要的是永远不要调用 .ConfigureAwait(false)
,除非您知道在非 UI 线程中恢复是安全的。 (但可以使用.ConfigureAwait(true)
)。
它还使您的代码更简单。这就是你所需要的:
private async void Run_Clicked( Object sender, EventArgs e )
User compare = new User( this.Parser1, this.Parser2 );
busyIndicator.IsBusy = true;
await Task.Run( () => compare.compare() );
busyIndicator.IsBusy = false;
MessageBoxResult result = Xceed.Wpf.Toolkit.MessageBox.Show( "Finished", "Generation Finished", MessageBoxButton.OK );
也就是说,因为await
将重新抛出任何异常,您应该始终在finally
块内恢复表单的状态:
private async void Run_Clicked( Object sender, EventArgs e )
User compare = new User( this.Parser1, this.Parser2 );
busyIndicator.IsBusy = true;
try
await Task.Run( () => compare.compare() );
MessageBoxResult result = Xceed.Wpf.Toolkit.MessageBox.Show( "Finished", "Generation Finished", MessageBoxButton.OK );
finally
busyIndicator.IsBusy = false;
第 2 部分:报告进度
要向调用者指示进度信息,请使用IProgress<T>
。 IProgress<T>
的类型参数应该是一个不可变值或对象(即像int
或float
这样的值类型,像string
这样的不可变引用类型,@987654342 @,或具有仅 readonly
字段的自定义类对象)。
float
(又名Single
)作为T
(来自0-1.0
的值代表0-100%
.
我不建议使用int
来表示百分比,因为这意味着进度条的“分辨率”会太低(只有 100 步),而不是从 0 到 100% 的小数位平稳地进行,例如0.1%、55.55%、99.95% 等。
如果您想返回带有百分比的String
消息,请使用ValueTuple
:IProgress<(String message, float percentage)>
。
首先,更改您的compare
方法(应命名为Compare
,顺便说一句)以接受IProgress<float>
并使用progress?.Report( value )
报告进度(使用?.
运算符允许调用者选择退出提供IProgress
对象):
class User
public void Compare( IProgress<float> progress = null )
for( Int32 i = 0; i < 100; i++ )
progress?.Report( i / 100f );
progress?.Report( 1f ); // 100% completion
在您的 WPF 代码中,我们将使用 .NET 中 IProgress<T>
的默认实现,称为 Progress<T>
which provides UI thread synchronization built-in:
提供给构造函数的任何处理程序或使用 ProgressChanged 事件注册的事件处理程序都通过在构造实例时捕获的 SynchronizationContext 实例进行调用。
像这样:
class MyWpfWindow
private void UpdateProgressBar( float value )
this.progressBar.Value = value * 100; // Assuming ProgressBar's scale is 0-100.
private async void Run_Clicked( Object sender, EventArgs e )
User compare = new User( this.Parser1, this.Parser2 );
busyIndicator.IsBusy = true;
Progress<float> progress = new Progress<float>( this.UpdateProgressBar );
try
await Task.Run( () => compare.Compare( progress ) );
MessageBoxResult result = Xceed.Wpf.Toolkit.MessageBox.Show( "Finished", "Generation Finished", MessageBoxButton.OK );
finally
busyIndicator.IsBusy = false;
【讨论】:
好的.. 我如何在进度条 GUI 中实时显示compare.compare()
的进度?而不是使用busyIndicator
@RDN 抱歉,我完全忽略了想要更新进度条的部分。我已经更新了我的答案,演示了如何使用 IProgress<T>
和 Progress<T>
来安全地提供进度更新。以上是关于无法从使用 c# 在不同线程上运行的另一个页面 User.cs 更新 GUI (MainWindow.xaml) 中的进度条的主要内容,如果未能解决你的问题,请参考以下文章
.net C# 将值作为查询字符串发布到带有按钮的另一个页面