无法从使用 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

Microsoft Windows 上的线程很昂贵(不像 Linux 上的线程通常很便宜)。 使用线程池,其中包含已由 .NET 创建并等待工作的线程。 默认情况下,使用Task.Run 将在线程池线程中运行作业,并将作业表示为Task 对象,您可以安全地使用await,因为WPF 的调度程序支持Task 调度程序并且将始终恢复在await 之后的 UI 线程上。 请注意,尽管这意味着使用 asyncawait 关键字,但这段代码实际上并不是“真正的”异步代码(与网络套接字和文件系统上的真正异步 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&lt;T&gt;IProgress&lt;T&gt; 的类型参数应该是一个不可变值或对象(即像intfloat 这样的值类型,像string 这样的不可变引用类型,@987654342 @,或具有 readonly 字段的自定义类对象)。

我假设您只想报告一个百分比值,因此我们将使用float(又名Single)作为T(来自0-1.0 的值代表0-100% . 我不建议使用int 来表示百分比,因为这意味着进度条的“分辨率”会太低(只有 100 步),而不是从 0 到 100% 的小数位平稳地进行,例如0.1%、55.55%、99.95% 等。 如果您想返回带有百分比的String 消息,请使用ValueTupleIProgress&lt;(String message, float percentage)&gt;

首先,更改您的compare 方法(应命名为Compare,顺便说一句)以接受IProgress&lt;float&gt; 并使用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&lt;T&gt; 的默认实现,称为 Progress&lt;T&gt; 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&lt;T&gt;Progress&lt;T&gt; 来安全地提供进度更新。

以上是关于无法从使用 c# 在不同线程上运行的另一个页面 User.cs 更新 GUI (MainWindow.xaml) 中的进度条的主要内容,如果未能解决你的问题,请参考以下文章

在 C# 中的另一个对象内实例化用户定义的对象

将数据从UI线程传递到c#中的另一个线程

.net C# 将值作为查询字符串发布到带有按钮的另一个页面

在C#的同一个异步线程中运行不同的任务[重复]

使用 Jquery 加载函数从空 div 中的另一个页面加载 div

将 Ansible 变量从一个角色(在一个主机上运行)传递给在同一剧本中的另一台主机上运行的另一个角色