65.9K
CodeProject 正在变化。 阅读更多。
Home

响应式 GUI 应用程序技巧

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.90/5 (10投票s)

2016年7月27日

CPOL

3分钟阅读

viewsIcon

19837

downloadIcon

198

如何避免在GUI应用程序中阻塞长时间操作,导致应用程序挂起。

引言

通常,在协程中实现长时间操作(作为空闲任务处理)会更好。 可下载的代码中提供了对该解决方案的支持。

背景

在GUI应用程序中阻塞长时间操作(例如高级数据解析)是令人讨厌的,它们会导致窗口冻结和其他GUI元素。 此代码的目的是确保在同一线程中进行非阻塞的长时间操作,而无需考虑多线程问题。

例如,在MFC框架中有一个可重写的OnIdle方法。 在此解决方案中,可以使用来自关联源的接口Tasks::IIdleTask来重写它。


ExampleApp.h

// ...
//=============================================================================
class ExampleApp: public CWinApp
{
public:
   BOOL OnIdle( LONG _counter ) override;
   // ...
   void ScheduleIdleTask( boost::shared_ptr< Tasks::IIdleTask > const & _task );
   // ...
private:
   std::queue< boost::shared_ptr< Tasks::IIdleTask > > m_idleTaskQueue;
   // ... 
};


ExampleApp.cpp

//------------------------------------------------------------------------------------
BOOL ExampleApp::OnIdle( LONG _counter )
{
    BOOL moreFrameworkTasks = CWinApp::OnIdle( _counter );  // default implementation

    if( !m_idleTaskQueue.empty() )
    {
        boost::shared_ptr< Tasks::IIdleTask > task = m_idleTaskQueue.front();
        task->run();
        if( task->isDone() )
           m_idleTaskQueue.pop();
    }
    return moreFrameworkTasks || !m_idleTaskQueue.empty();
} 

//------------------------------------------------------------------------------------
void ExampleApp::ScheduleIdleTask( boost::shared_ptr< Tasks::IIdleTask > const & _task )
{
    m_idleTaskQueue.push( _task )
}

通常,应派生实现接口Tasks::IIdleTask的类,应重写run方法。 isDone方法应指示操作是否完成。

但这个想法更进了一步。 类Tasks::IdleCoroutineTask(实现接口Tasks::IIdleTask)可用于将一个可让步协程安排为空闲任务。 在这种情况下,一个合适的函数可以在“行之间”进行任何长时间操作,同时处理GUI消息。 但是,计划的函数负责适当地频繁调用接口Tasks::IYieldyield方法,该方法作为参数传递。 否则,GUI行为可能会受到干扰,或者至少受到影响。

Tasks::IYield::yield方法可以在两种模式下调用

  • 启发式 - 仅当协程使用了至少200毫秒的CPU时,才会发生让步,因此,过度调用比很少调用要好。
  • 无条件 - 总是会发生让步

无论空闲时间处理如何,协程在其他问题中都是一个有用的实用程序,例如智能迭代器。 要显式使用协程,应使用Tasks::Coroutine类,它仅由静态create方法实例化。 它不会自动将控制权传递给协程,而应使用switchTo方法。

在Windows中,协程基于fiber系统机制。 由于每个fiber都使用其自己的堆栈,因此频繁创建fiber可能会影响系统性能。 因此,实现了fiber的; 每次不是创建和删除fiber,而是始终保持随时可用的fiber。 创建协程时,从池中获取一个fiber并关联,以后在协程完成后将其回收。

使用代码

先决条件

  1. 应创建MFC应用程序(或Qt,并可以调整代码)。
  2. 应将下载的代码包含到项目中
  3. 应以类似于ExampleApp的方式修改应用程序类

稍后可以按以下方式完成

#include "coroutine_function.hpp"
#include <boost/shared_ptr.hpp>

//
//-----------------------------------------------------------
void longOperarion( Tasks::IYieldRef const & _refYield )
{
    bool done = false;

    while( !done )
    {
        // ... any actions

        _refYield.get().yield();  // By default heueristic mode
                                  // in fact standard message handling will be done

        // Suppose, getNextFileName returns the next file to be processed
        std::ifstream file( getNextFileName(), std::ifstream::in );  

        while( !file.eof() )
        {
            std::string line;
            std::getline( file, line );

            // ... any actions

            _refYield.get().yield(); // Redundant yields are reduced due to heueristic
        }
    }
} 

// ... any code

//----------------------------------------------------------- 
void ExampleDlg::OnDoOperation()
{
    boost::shared_ptr< Tasks::IdleCoroutineTask > idleTask( 
            new Tasks::IdleCoroutineTask( &longOperarion )
        );

    theApp.ScheduleIdleTask( idleTask );
}

 

此外,计划的函数可以包含一个参数

#include "coroutine_function.hpp"
#include <boost/shared_ptr.hpp>
#include <boost/bind.hpp>

//
//-----------------------------------------------------------
void longOperarionWithParameter( 
      Tasks::IYieldRef const & _refYield
    , boost::shared_ptr< Parameter > const & _parameter
   )
{
    // ... any actions
} 

// ... any code

//----------------------------------------------------------- 
void ExampleDlg::OnDoOperation()
{
    boost::shared_ptr< Parameter > parameter( new Parameter() );

    // ... any actions with parameter initialization

    boost::shared_ptr< Tasks::IdleCoroutineTask > idleTask( 
            new Tasks::IdleCoroutineTask( 
                boost::bind( &longOperarionWithParameter, _1, parameter )
             )
        );

    theApp.ScheduleIdleTask( idleTask );
}

 

关注点

  1. 参数Tasks::IYieldRef定义为boost::reference_wrapper<Tasks::IYield>。 这是一个针对boost::bind使用的引用的意外行为的解决方法。
  2. 协程不应被其他协程使用 - 这可能会导致不可预测的结果。 除非可以调整代码。
  3. 为了在多线程环境中使用,应稍作调整代码(Tasks::FiberPoolTasks::FiberInfo::ms_mainFiber都应更改为线程局部变量)。
  4. Coroutine中工厂方法create的目标是确保该类不会以共享指针可访问的其他方式实例化,这可能会导致不可预测的结果。

历史

2016-07-27 第一个版本。

2016-07-29 介绍改进。

© . All rights reserved.