无需冻结gui的简单任务

Posted

技术标签:

【中文标题】无需冻结gui的简单任务【英文标题】:Simple task without freezing the gui 【发布时间】:2012-10-24 17:10:17 【问题描述】:

我遇到了一个小问题。我有两个线程,一个执行一个循环,每次都需要向 GUI 的线程返回/发送一个数字。为此,我使用 BackGroundWorker ReportProgress

让我们这样说:

我有一个 BackGroundWorker ,它执行 (DoWork) 一个从 0 计数到任何值的简单循环。循环的每个条目我使用 ReportProgress 事件将计数器发送到 GUI 线程,该线程将打印计数器的值。

    void worker_DoWork(object sender, DoWorkEventArgs e)
    
        int count = 0;
        BackgroundWorker Worker = (BackgroundWorker)sender;

        while (count < 10000000)
        
            Worker.ReportProgress(count);
            count++;
        
    

    void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    
        txt.Text = e.ProgressPercentage.ToString();
    

现在,这个选项冻结了 GUI。

我知道 ReportProgress 正在创建 BackGroundWorker 的线程上调用 ProgressChange 处理程序,因此我认为循环执行得如此之快,因此 GUI 的线程无法成功将值打印为必填。

如何在不冻结 GUI 的情况下执行这样的任务?

我听说过 Dispatcher,但我不确定它的用途。

【问题讨论】:

你的意思是 GUI 对吗?因为据我所知 GUID 是完全不同的东西 :-) 另外,您使用的是 WinForms 吗?或者我可以假设 WPF 吗? GUI = 图形用户界面; GUID = 全球唯一标识符...仅供参考;) 【参考方案1】:

问题是每次发生变化时您都会调用reportProgress。只有在“需要”报告进度时才应该调用它。请参阅 MSDN http://msdn.microsoft.com/en-us/library/ka89zff4.aspx。 把你的工作改成这样:

 while (count < 10000000)
    
        if ((count % 1000) == 0)
            Worker.ReportProgress(count);
        count++;
    

这将在每处理 1000 个项目后调用 ReportProgress,因此不会给您的 GUI 线程带来不必要的负载

【讨论】:

是的,我知道这一点。但这不是我原来的循环,我原来的循环做了比这更大的事情,需要向 GUID 的线程返回一个值。【参考方案2】:

您的示例代码试图以比 GUI 处理更新通知消息的速度快得多的速度更新 GUI,因此 GUI Windows 消息队列中充斥着 gunge 并阻止它处理其他消息 - GUI 冻结。

在非 GUI 线程中监控高速操作的进度是少数情况下轮询是更好的解决方案之一。使用 Forms.Timer 事件读取并显示“currentProgress”值,该值可能由线程的方法返回。 500 毫秒是一个合理的计时器值 - 人类用户无法以比这快得多的速度跟上编辑/文本框中不断变化的整数值。

'理想情况下', currentProgress 值的读/写应该被锁定,也许使用原子操作,但如果你只是每 500 毫秒读取一个 int,如果'真正' 线程的功能意味着进度计数不太可能连续缓存在寄存器中。

【讨论】:

谢谢,我也是这么想的,所以最后一件事,创建backgroundWorker的线程可以读取进度计数器吗? 我的意思是,创建backgroundworker的线程可以访问属于backgroundworker线程的currentProgress值吗?【参考方案3】:

在不冻结 GUID 的情况下如何执行类似的任务?

使用调度程序让我假设您正在使用 WPF,无论如何,它会是:

void worker_DoWork(object sender, DoWorkEventArgs e)

    int count = 0;
    BackgroundWorker Worker = (BackgroundWorker)sender;

    while (count < 10000000)
    
        Worker.ReportProgress(count);
        count++;
    


void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)

    Dispatcher.BeginInvoke((Action)(() =>  txt.Text = e.ProgressPercentage.ToString(); ));

调用 Dispatcher.BeginInvoke 会导致给定的 Action 在 UI 线程上实际执行,确保不会因 UI 线程访问 UI 元素之外的线程的原因而引发异常。

另外,你可以试试这个,作为替代by using a task.

【讨论】:

在创建后台工作线程的线程中调用 ProgressChanged 事件处理程序。但即使它不是在 UI 线程中创建的,它也会引发异常,而不是阻塞 UI。 那行不通,但我真的不明白你为什么选择这样做? @Matan 我在考虑线程安全,调用调度程序可以 100% 确保操作是在 UI 线程上执行的。如果您将 Thread.Sleep(500) 放入循环中,您会看到 UI 相应地更新吗? 调度员在这里做什么? @Matan 查看上面的帖子和链接。这基本上是调度程序的使用,确保您在 UI 线程上执行该活动。

以上是关于无需冻结gui的简单任务的主要内容,如果未能解决你的问题,请参考以下文章

当 OnClickListener.onClick 中出现 NullPointerException 时 Android 冻结(其他任务无法启动)

运行另一个进程而不冻结 GUI

Firebase 使任务仅在主线程上执行,导致 Unity 冻结

如何修复 PyQt5 GUI 冻结

为啥即使使用 QThreadPool,GUI 也会冻结?

X11/XWINDOW GUI窗口应用在任务栏上没有显示的解决办法