【问题标题】:Search/Filtering functionality blocks the Main(GUI) thread搜索/过滤功能阻止主(GUI)线程
【发布时间】:2021-09-20 19:14:48
【问题描述】:

我有一个自定义表格,并且我已经实现了一个搜索/过滤功能,该功能会遍历表格中的所有元素,然后根据该项目/元素是否与我们正在搜索的项目/元素匹配来隐藏/显示表中的项目. 例如,假设我有一个文本控件并在其中键入“wxwidgets”,然后我的自定义函数将遍历表中的所有元素并隐藏与此“wxwidgets”条目不匹配的元素。这工作正常,我能够正确地隐藏/显示元素。但问题是这个搜索阻塞了主线程(gui),因为我在主线程中执行此操作。该表有大约 1000 个条目,或者将来可能更多。我的问题是如何避免主线程的这种阻塞。我正在考虑使用另一个工作线程来搜索元素。但后来我读到“没有辅助线程应该调用 gui 函数”。但是,我怎样才能从工作线程中显示/隐藏表格的元素。例如,当前在主线程中,我使用 Show(true) 或 Show(false) 显示/隐藏表中的特定条目,所有这些都是在我处于 for/while 循环时完成的。但是,如果我在工作线程中实现这个(for 循环),那么根据引用的建议,我不应该使用该工作线程内部的 Show() 函数。在这种情况下可以做些什么?此外,是否有任何其他方式/建议可以搜索表格元素。每次用户在文本字段中输入一些文本时,我都在考虑启动一个新的可拆卸线程。如果用户将更多文本附加到文本字段并启动一个从头开始搜索的新线程,则删除该旧线程。这是正确的解决方案吗?

问题是在我的 for 循环中我正在使用 wxWidgets 函数。例如,这就是我的 for 循环的样子:

void AnotherClass::onTextChanged(wxCommandEvent &event)
{
    for(int i = 5; i<154;++i)
    {                                                                                                                        
        SomeClass *element = dynamic_cast<SomeClass*>(FindWindowById(i));
        if(element.GetLabel() == textEnteredInTextCtrl)                                          
        {                                                                                                                    
            element.Show(true);//element found                                                        
            //update the necessary layout here using layout call                                
        }                                                                                                                    
        else                                                                                                               
        {                                                                                                                     
            element.Show(false);//element not found                                                  
            //update the necessary layout here using Layout() call                             
        }                                                                                                                     
    }                                                                                                                        
}   

这是搜索的主要部分。现在在工作线程内部应该/我可以使用 FindWindowById() 和 GetLabel() 之类的函数吗?它们是否被视为 GUI 函数,以便我可以从工作线程中使用它们?我可以或不可以从工作线程内部使用 FindWindowByID() 和 GetLabel() 以及其他类似的函数(如 Layout() 和 Show())。我应该如何进行这项工作?我的意思是我知道如何使用 wxThread 并使用 QueueEvent 发送事件,并且我的程序中已经有另一个工作线程来执行一些其他计算,但我想知道在这种特殊情况下我应该如何让它工作。

QuentinC 建议的另一个解决方案是使用计时器。他的建议如下:

就我而言,我不会在 用户在搜索框中输入了一个字母。相反,当用户有 键入一个字母(wxEVT_TEXT),我启动或重新启动一个 500 毫秒的计时器。仅有的 当计时器结束(用户停止输入 500 毫秒)然后列表 被刷新。同样,这是一种避免快速接替 无用的刷新。

但在这种使用计时器的情况下,我有几个查询。我将 wxEVT_TEXT 从 CustomTextCtrl 的 onTextChanged 方法发送到此类的 onTextChanged 方法。我想当用户键入一个字母时,我可以在 CustomTextCtrl 类的 onTextChanged 方法中启动 500 毫秒的计时器。但是我应该在哪里检查计时器是否仍在运行?在 CustomTextCtrl 的 onTextChanged 方法中还是在 AnotherClass 的 onTextChanged 方法中? 所以澄清我有两个类

  1. CustomTextCtrl 类有一个 onTextChanged 方法,该方法使用 event.Skip() 将此事件转发给它的父级。
  2. 父类AnotherClass也有一个onTextChanged方法,这个方法接收这个转发的事件并进行搜索和更新表。

我应该在哪里以及如何启动/重新启动/停止计时器以更新 UI?

注意:过滤元素的过程运行良好,但唯一的问题是当用户在 textctrl 中键入一些文本时主(GUI)线程被阻塞。让我们说 6 或 7 秒后,文本出现在 textctrl 中,并且 UI 被更新。我不希望主 UI 在 6-7 秒内无响应。

另外,请注意我没有使用任何 wxList/wxGrid。我只是在使用 wxPanels 和 wxStaticText 并在它们上使用显示/隐藏。

编辑: 上面代码的一个改进是只使用来自 for 循环外部的 Layout() 调用。如果我从 for 循环外部使用 Layout() 调用,那么搜索功能几乎可以立即工作。但是如果表有更多元素,这个(方法)仍然有可能在将来阻塞主线程。所以我想使用线程或计时器方法。但我不知道辅助线程如何使用 gui 函数,或者我如何/应该使用 wxTimer 方法(如果有的话)来解决这个问题。

【问题讨论】:

  • 我要问自己的第一个问题是为什么过滤 1000 个项目这么慢?在任何合理的现代 CPU 上,我希望它比 6-7 秒快得多(6-7 毫秒似乎是正确的)。我建议在过滤操作期间对代码进行概要分析,以找出“热点”在哪里——它可能在某处做一些非常低效的事情。
  • 顺便说一句,使用多线程可能不是一个好主意;它会使您的代码复杂化(并且可能会引入竞争条件)并且无论如何都不能真正解决根本问题,即您的视图需要很长时间才能更新。多线程更新仍然需要同样长的时间(尽管用户可以在更新期间与窗口进行交互,但是当视图过时时这并不是那么有用)
  • @JeremyFriesner 第一个问题的答案是:假设我尽可能快地输入“wxwidgets”。所以让我们看看会发生什么。首先,将调用字母“w”的 onTextChanged 方法,并将其转发给 AnotherClass' 方法,该方法将遍历所有(1000)表元素。同时我已经在“w”之后输入了字母“x”,所以另一个 wxEVT_TEXT 将启动并再次遍历所有表格元素。 “wxwidgets”的每个字母都会发生这种情况,因此很明显它会阻塞主线程,因为旧的 for 循环已经在进行了。
  • @JeremyFriesner 我已经将 layout() 调用移到了 for 循环之外,现在搜索功能几乎可以立即运行。但我仍在寻找线程或计时器方法来解决此问题,因为将来该表可能会有更多元素。
  • 除非你有一个 IO 绑定计算或一个非常复杂/高级的计算要做,否则我不认为计算会很慢。实际上,并不比用户输入慢。对于大文本,我同意,尽管有时可以执行增量计算(或在大多数情况下至少部分增量)。

标签: c++ multithreading user-interface event-handling wxwidgets


【解决方案1】:

我对这个潜艇有几个想法

A) 如果您遇到速度问题,那么您最好分析您的代码,看看瓶颈在哪里。

B) 从非主线程调用 GDI 函数是有风险的。也许只是要求 window-id 及其标签并不那么危险,但我认为调用Show() 绝对是。

C) 这段代码主要与 GUI 相关。我不认为工作线程在这里有用。但是堆叠类似的调用可能会提高它的速度。为此,我有三个建议:

    1. 使用CallAfter() 传递elemnt.Show() 方法
    1. 在循环之前使用Freeze(),在循环之后使用Thaw
    1. 只调用一次Layout(),就在循环之后。关于这个我想知道Show()/Hide() 控制是否比Enable()/Disabñe() 更好

D) 因为你调用了很多次FindWindowById(),而且还有很多用户更改,所以最好将所有受影响的窗口缓存在一个容器中(std::mapid 作为键)。然后,在循环内使用容器而不是 'FindWindowById()`。

E) 作为最后一个资源,如果 GUI 仍然被阻塞,请使用 wxYield() 每 xxx(比如 100)次循环迭代。根据未决消息,此解决方案可能会使事情变得更糟(重入、交叉影响等)。

【讨论】:

    猜你喜欢
    • 2012-05-05
    • 2019-12-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-30
    • 2020-03-12
    • 1970-01-01
    • 2019-03-05
    相关资源
    最近更新 更多