【问题标题】:Is there a way to slow down a BackgroundWorker in WPF?有没有办法减慢 WPF 中的 BackgroundWorker?
【发布时间】:2018-04-08 22:51:32
【问题描述】:

在某些游戏中,有一个从服务器下载内容的启动画面。它可能会在您等待时提供一些提示。我正在做类似的事情,只是加载发生得非常快,但我希望它再等几秒钟。

当用户第一次加载我的应用程序时,它有一个带有进度条的屏幕。目前,它检查服务器是否在线。如果是,则显示“已连接!”但是,它会立即淡出我的控件。我希望它再等 5 秒,以便读者阅读。然后淡出控件。

    private void frmMain_Loaded(object sender, RoutedEventArgs e)
    {  
        // Start background worker
        m_bgWorker = new System.ComponentModel.BackgroundWorker();
        m_bgWorker.ProgressChanged += M_bgWorker_ProgressChanged;
        m_bgWorker.WorkerReportsProgress = true;
        m_bgWorker.RunWorkerAsync(); 
        m_bgWorker.ReportProgress(100); 
    } 


    private void M_bgWorker_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
    {
        progConnect.Value = 0; 

        if (SystemBO.IsOnline())
        {
            lblConnection.Content = "Connected!"; 
        }

        progConnect.Value = e.ProgressPercentage;

        // TODO: Wait 5 seconds here...

        // Fade out controls
        lblTitle.BeginAnimation(Label.OpacityProperty, doubleAnimation);
        progConnect.BeginAnimation(Label.OpacityProperty, doubleAnimation);
        lblConnection.BeginAnimation(Label.OpacityProperty, doubleAnimation);
    }

注意:

  • 我试过System.Sleep(),但没有任何区别。我明白为什么。不过想法是一样的:我希望后台工作人员在完成之前休眠 5 秒。

解决方案:

我添加了更多事件:DoWork 和 RunWorkerCompleted。

然后我添加了这段代码:

    private void M_bgWorker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
    {
        for (int i = 0; i < 100; i++)
        { 
                m_index++;
                System.Threading.Thread.Sleep(40);
                m_bgWorker.ReportProgress(m_index);
        }

        System.Threading.Thread.Sleep(3000);  // wait 3 seconds to read 
    }

成功了。进度条的动画非常流畅。

【问题讨论】:

    标签: c# wpf


    【解决方案1】:

    您不应该在 ProgressChanged 处理程序中进行淡出。该处理程序实际上应该只用于更新进度信息,如文本和百分比条。

    您的淡出逻辑应移至单独的 RunWorkedCompleted 事件处理程序。在这里,您可以让线程休眠/等待计划任务一段时间,然后开始淡出控件。 (这里使用async/await而不是Thread.Sleep()的好处是在等待时不会阻塞UI线程,所以UI在等待时间过去时仍然保持响应。)

    它还确保在最后调用代码,而不是每次工作人员报告任何进度时。而且它也更干净,因为它允许您通过检查 RunWorkedCompletedEventArgs 来处理不同的终止状态:

    • 如果设置了e.Error,则工作线程发生异常。
    • 如果e.Cancelled 为真,则通过在工作线程上调用CancelAsync() 取消工作线程。 (仅当 WorkerSupportsCancellation 设置为 true 且仅当 DoWork 处理程序中的代码实际检查取消标志时才有用)
    • 否则一切正常。

    旁注:

    我假设您的代码不是一个完整的示例,因为您没有分配给DoWork 事件的实际处理程序。所以现在它不会做任何事情。从主线程调用ReportProgress() 也是错误的。该方法旨在从DoWork 事件处理方法中调用,以便让异步线程将状态更新报告回 UI,因为这些事件是在主线程上处理的。

    【讨论】:

    • 我注意到 BackgroundWorker 有一些额外的事件,所以我尝试了这些。工作得更好,是的!
    • 在相关说明中,如果您使用的是 .NET 4.5 或更高版本,您可能希望考虑使用 Task.Run 而不是 BackgroundWorker 来完成整个事情。这似乎是一种更现代、更直接的方法,避免了所有不同事件处理程序的麻烦:blog.stephencleary.com/2013/05/…
    • 我相信我尝试过Task,但由于它在单独的线程上运行,它无法访问控件,因为主线程可以控制它。有点心疼,然后就遇到了BackgroundWorker。
    • @Bob 这就是 Dispatcher 的用途。 Dispatcher.Invoke 将在 UI 线程上运行函数
    • 查看我发布的链接的第 5 点。要报告任务的进度,您可以使用委托函数实例化 Progress 对象,当您在对象上调用 Report() 方法时会调用该委托函数。与后台工作人员相比的好处是报告的对象可以是任何类型,而不仅仅是整数和字符串。
    【解决方案2】:

    而不是frmMain_Loaded中的最后一行:

    m_bgWorker.ReportProgress(100); 
    

    我会在 Sleep() 中添加一个 for 循环,用于模拟一些额外的处理:

    for (int i=0; i<=100; i+=10)
    {
        if (SystemBO.IsOnline())
            i = 100;
        Sleep(1000);
        m_bgWorker.ReportProgress(i);
    }
    

    【讨论】:

    • 使用Thread.Sleep 几乎总是一个坏主意。 await Task.Delay(TimeSpan.FromSeconds(1.0)); 会好很多。
    • @Enigmativity: 我试过了,但得到了这个错误: System.InvalidOperationException: '这个操作已经调用了 OperationCompleted 并且进一步的调用是非法的。'
    • @Bob - 听起来你在重用Task.Delay。可以出示一下代码吗?
    • @Bob - 无论如何Sleep 不是答案。
    • 故意延迟的任务建议异步操作只是一种狂热的反应。异步并不是所有事情的答案。睡眠适用于这个特定的用例,因为在显示进度对话框时没有其他任何事情要做。如果有用户要执行的操作(不寻常),如果您更改睡眠持续时间,UI 仍会在 1 秒或更短的时间内响应。
    猜你喜欢
    • 1970-01-01
    • 2014-01-01
    • 2020-12-04
    • 1970-01-01
    • 2022-01-16
    • 2013-04-26
    • 1970-01-01
    • 2017-05-20
    • 2011-01-22
    相关资源
    最近更新 更多