通用任务/工作者实现






4.67/5 (2投票s)
一个通用的任务/工作者实现,为您的对象提供异步“即发即弃”机制
引言
以下文章讨论了一个简单的任务/工作者实现。它遵循为您的应用程序/对象提供异步“即发即弃”风格机制的思路。假设读者了解以下 TR1 概念;可调用对象 (std::tr1::function
), 函数绑定 (std::tr1::bind
)。如果没有这些先决条件,这篇文章可能会令人困惑。
背景
我遇到的大量涉及线程的代码通常表现出对象与线程之间的“is-a”关系。这基本上意味着对象“是一个”线程。在某些情况下,实现对象与其线程之间的“has-a”关系是有意义的。从表面上看,两者似乎都实现了相同的功能,但后者提供了一种更模块化的方法。
传统的编码实践包括,从某个“CThread
”类派生您的对象,并实现某种“Run()
”函数,该函数封装了需要完成的异步工作量。在大多数情况下,“is-a”方法可以奏效,但如果您能将两者分离呢?像往常一样设计一个对象,为其编写代码,然后将异步功能附加到对象中的任何函数。
传统用法
class threaded_object: public my_base_thread_class
{
public:
threaded_object(){};
~threaded_object(){};
protected:
// This would be the overloaded function from your base class
// that needs to be implemented. This represents the work performed
// by the worker.
virtual int run()
{
/* do some work */
return 0;
}
}
用法将是
threaded_object my_object;
my_object.create_thread(); >-- This would create the thread and call the Run() function.
让我们看看如何使用通用任务/工作者实现以稍有不同的方式完成此操作。通用任务/工作者对象是一个简单的存根,它在单独的线程上执行可调用对象,表示工作量。该类在“utility
”命名空间中呈现,称为“generic_worker
”,它可以执行的可调用对象的类型由函数类型“utility::generic_worker_task
”表示。
namespace utility
{
// The template class
template<t_fn>
class generic_worker_t
// This represents the type of callable object ( the quantum of work )
// that the generic_worker class
// can execute
typedef std::tr1::function<void()>generic_worker_task;
// Specialized generic_worker
typedef generic_worker_t<generic_worker_task>generic_worker;
}
“generic_worker
”对象期望传入的可调用对象属于类型“generic_worker_task
”,它不接受任何参数,也不返回任何内容。您类中的任何函数/成员函数都可以使用称为“bind
”(std::tr1::bind
)的操作转换为可调用对象。bind
操作返回一个可调用对象,该对象具有已经绑定到该函数的预定义参数。唯一的问题是返回值。您的类中的函数可能会返回值,并且从这些函数创建的可调用对象不能包含在“generic_worker_task
”类型对象中,因为它具有返回值。为此,您可以轻松地将“generic_worker_t
”模板化以接受适当的可调用对象类型。例如,如果您的函数返回“int
”,您可以如下所示专门化“generic_worker_t
”
typedef std::tr1::function<int()>generic_worker_task_int;
// Specialized generic_worker
typedef generic_worker_t<generic_worker_task_int>generic_worker_int;
Using the Code
假设您有一个以下形式的类
class my_object
{
public:
int function_a();
int function_b();
int function_c();
}
假设这些函数中的每一个都需要很长时间才能完成。例如,想象一下您想直接从 GUI 线程调用这些函数,如果您这样做,它会导致消息循环停止传递消息并阻塞,从而使 UI 无响应,因此需要将此工作卸载到另一个线程。所举的例子只是众多用例之一。
步骤 1:创建您的模板化通用工作者对象。在这种情况下,所有函数都返回int
,因此generic_worker
对象将专门化为可调用对象,该对象不接受任何参数并返回一个int
。
typedef std::tr1::function<int()>generic_worker_task_int;
// Specialized generic_worker
typedef generic_worker_t<generic_worker_task_int>my_generic_worker;
步骤 2:将此工作者包含在您的主对象中
class my_object
{
public:
int function_a();
int function_b();
int function_c();
protected:
my_generic_worker async_obj;
}
步骤 3:为异步行为创建存根,其中async_[函数名]
表示该函数的非阻塞或异步版本。仅为需要异步行为的那些函数创建存根。
class my_object
{
public:
// async stubs
int async_function_a(int arg1);
int async_function_b(bool arg1);
// actual functions
int function_a(int arg1);
int function_b(bool arg1);
int function_c();
}
步骤 4:实现存根。这是您将使用 bind 操作来绑定函数参数的地方。
根据您希望如何处理传入的可调用对象指针,您可以调用不删除传入的可调用对象指针的“do_work
”函数,或删除传入的可调用对象的“do_work_and_clean()
”函数。
int my_object::async_function_a(int arg1)
{
generic_worker_task_int* p = new( generic_worker_task_int );
if( p )
{
*p = std::tr1::bind(&my_object::function_a,this,arg1);
async.do_work_and_clean(p, THREAD_PRIORITY_NORMAL);
}
return 1;
}
int async_function_b(bool arg1)
{
generic_worker_task_int* p = new( generic_worker_task_int );
if( p )
{
*p = std::tr1::bind(&my_object::async_function_b,this,arg1);
async.do_work_and_clean(p, THREAD_PRIORITY_NORMAL);
}
return 1;
};
步骤 5:调用异步存根现在将异步执行该方法。
my_object instance_of_my_object;
instance_of_my_object.async_function_a(1);
async_
版本的调用返回给调用者,工作者线程执行实际任务。由对象开发人员设计父对象进行多线程处理,并处理并发问题等。“generic_worker
”不会遗弃任何正在运行的任务,并且会在其所有任务线程完成之前等待销毁。
“generic_task
”对象可能需要同步,需要并发的部分用注释标记。通过添加锁/关键节来保护这些部分可能是有意义的。
历史
- 2010 年 4 月 28 日 - 修订版 1