目录
考虑两个类之间的异步场景:客户端调用类A处理某个任务,A需要委托B进行一些处理并返回结果;并且都是异步进行,即函数迅速返回之后在后台进行计算,完毕后通知调用者。
一、使用函数指针
类B需要有SetCallBack接口来接收函数指针,此处我们用std::function来实现。
B在进行ProcessAsync任务时另起一个线程并去进行较长时间的后台计算(此处用std::this_thread::sleep_for来模拟长时间计算),计算完成后通过刚才设置进来的函数指针来通知调用者。
#include <iostream>
#include <functional>
#include <thread>
class B
{
public:
void SetCallBack(const std::function<void(double)> &cb) {
m_callback = cb;
}
void ProcessAsync() {
printf("class B, create thread to compute and return immediately\n");
m_th = std::thread(&B::ProcessReal, this);
}
~B() {
printf("begin to destroy B\n");
if (m_th.joinable()) {
m_th.join(); //析构时保证子线程join进来
}
printf("~B\n");
}
private:
void ProcessReal() {
printf("class B, Process for a long time...\n");
std::this_thread::sleep_for(std::chrono::seconds(3));
if (m_callback) {
printf("callback\n");
m_callback(3.72); //该函数指针可能是个野指针
}
}
std::function<void(double)> m_callback;
std::thread m_th;
};
类A在向类B设置回调时既可以用lambda表达式也可以用std::bind,建议用lambda。 A收到回调时打印计算结果result。
#include <iostream>
#include <memory>
#include <functional>
#include <mutex>
#include "B.h"
class A
{
public:
void DoTask() {
printf("class A, DoTask\n");
m_b = std::make_unique<B>();
m_b->SetCallBack([this](double res) {return OnProcessDone(res);}); //用lambda
//m_b->SetCallBack(std::bind(&A::OnProcessDone, this, std::placeholders::_1)); //用bind
m_b->ProcessAsync();
}
~A() {
printf("~A\n");
}
private:
void OnProcessDone(double result) {
std::unique_lock<std::mutex> lock(m_mtx);
printf("OnProcessDone, result = %f\n", result);
m_res = result;
}
std::unique_ptr<B> m_b{nullptr};
double m_res;
std::mutex m_mtx;
};
主函数就不多说了
#include "A.h"
int main() {
A a;
a.DoTask();
return 0;
}
这种方式有个问题是,B在回调A的成员函数时,可能A的实例已经被销毁,导致访问的成员变量已经不存在。解决方法是由A来管理B的生命周期,且在B析构时需要保证所有子线程都已经join进来。
二、新建类继承接口类并实现虚方法
B提供一个接口类Listener,接口类中有一个纯虚方法OnProcessDone;
#include <iostream>
#include <functional>
#include <thread>
#include <memory>
class B
{
public:
class Listener {
public:
virtual void OnProcessDone(double result) = 0;
};
void SetCallBack(const std::shared_ptr<Listener> &cb) {
m_callback = cb;
}
void ProcessAsync() {
printf("class B, create thread to compute and return immediately\n");
m_th = std::thread(&B::ProcessReal, this);
}
~B() {
if (m_th.joinable()) {
m_th.join();
}
printf("~B\n");
}
private:
void ProcessReal() {
printf("class B, Process for a long time...\n");
std::this_thread::sleep_for(std::chrono::seconds(3));
if (m_callback) {
printf("callback\n");
m_callback->OnProcessDone(3.72);
}
}
std::shared_ptr<Listener> m_callback;
std::thread m_th;
};
而在A中可以新声明一个内部类MyListener来继承B::Listener,并实现这个虚方法。
A::MyListener声明成内部类的好处是:内部类默认是外部类的友元,则A::MyListener就可以访问类A的私有成员,若回调需要访问私有成员时这种写法十分方便。
A中的MyListener需要存储A的指针用于访问A的成员,可以用weak_ptr或裸指针,但不要用shared_ptr,否则会出现循环引用、导致内存泄漏。(A保存了B的sharedptr,B保存了MyListener的sharedptr,MyListener又保存了A的sharedptr,谁也不会被释放)。
本例使用了weak_ptr,首先检查它是否过期(expired),然后再进行对应的操作,避免操作指向A的野指针。
#include <iostream>
#include <memory>
#include <functional>
#include <mutex>
#include "B.h"
class A : public std::enable_shared_from_this<A>
{
public:
void DoTask() {
printf("class A, DoTask\n");
m_b = std::make_shared<B>();
m_b->SetCallBack(std::make_shared<MyListener>(weak_from_this()));
m_b->ProcessAsync();
}
~A() {
printf("~A\n");
}
private:
class MyListener : public B::Listener {
public:
MyListener(std::weak_ptr<A> a) : m_a(a) {}
void OnProcessDone(double result) override {
printf("OnProcessDone, result = %f\n", result);
if (!m_a.expired()) {
std::shared_ptr<A> a = m_a.lock();
std::unique_lock<std::mutex> lock(a->m_mtx);
a->m_res = result; //内部类可以访问外部类的私有成员
}
}
private:
std::weak_ptr<A> m_a; //弱引用
};
std::shared_ptr<B> m_b{nullptr};
double m_res;
std::mutex m_mtx;
};
由于使用了std::enable_shared_from_this,所以主函数在生成A时也要改成智能指针:
#include "A.h"
int main() {
std::shared_ptr<A> a = std::make_shared<A>();
a->DoTask();
return 0;
}
三、直接继承接口类并实现虚方法
由于我们的类A对类B是强引用,为了避免把A设置给B产生循环引用,需要把B对listener的引用设置为弱引用:
#include <iostream>
#include <functional>
#include <thread>
#include <memory>
class B
{
public:
class Listener {
public:
virtual void OnProcessDone(double result) = 0;
};
void SetCallBack(const std::weak_ptr<Listener> &cb) {
m_callback = cb;
}
void ProcessAsync() {
printf("class B, create thread to compute and return immediately\n");
m_th = std::thread(&B::ProcessReal, this);
}
~B() {
if (m_th.joinable()) {
m_th.join();
}
printf("~B\n");
}
private:
void ProcessReal() {
printf("class B, Process for a long time...\n");
std::this_thread::sleep_for(std::chrono::seconds(3));
if (m_callback.expired()) {
printf("callback expired\n");
return;
}
printf("callback\n");
m_callback.lock()->OnProcessDone(3.72);
}
std::weak_ptr<Listener> m_callback; //弱引用
std::thread m_th;
};
#include <iostream>
#include <memory>
#include <functional>
#include <mutex>
#include "B.h"
class A : public B::Listener, public std::enable_shared_from_this<A>
{
public:
void DoTask() {
printf("class A, DoTask\n");
m_b = std::make_unique<B>();
m_b->SetCallBack(weak_from_this());
m_b->ProcessAsync();
}
~A() {
printf("~A\n");
}
private:
void OnProcessDone(double result) override {
printf("OnProcessDone, result = %f\n", result);
std::unique_lock<std::mutex> lock(m_mtx);
m_res = result;
}
std::unique_ptr<B> m_b{nullptr};
double m_res;
std::mutex m_mtx;
};
方法二和方法三的区别是判断引用失效的位置不同,一个是在新建的类中,一个是在被委托的类中。
四、使用std::future和std::async
这种方式已经没有回调了,做法是在类A中启动一个std::async,将B的同步处理函数变成异步的,这样就不需要向B设置回调了,B的实现会非常简单。 A中在需要计算结果时调用future的.get()就可以取到结果。
#include <iostream>
#include <thread>
class B
{
public:
double ProcessSync() {
printf("class B, Process for a long time...\n");
std::this_thread::sleep_for(std::chrono::seconds(3));
return 3.72;
}
};
要注意std::future是不可拷贝的,所以把future传到另一个线程时要move过去。
#include <iostream>
#include <memory>
#include <functional>
#include <future>
#include "B.h"
class A
{
public:
void DoTask() {
printf("class A, DoTask\n");
m_b = std::make_shared<B>();
std::future<double> fut = std::async(std::bind(&B::ProcessSync, m_b));
std::thread th(&A::WaitForProcessDone, this, std::move(fut));
th.detach();
}
private:
void WaitForProcessDone(std::future<double> &&fut) {
double result = fut.get();
printf("ProcessDone, result = %f\n", result);
m_res = result;
}
std::shared_ptr<B> m_b{nullptr};
double m_res;
};
想深入进阶了解的可以看这篇,chromium实现的高效回调 https://cloud.tencent.com/developer/article/1519851