前言
MethodCall类位于WebRTC的api/proxy.h中,如其名而知该类的作用是进行某个方法调用。实际上,是用来实现某个指定方法在指定线程上同步执行并返回结果的效果。其实该文件中并没有真正的MethodCall类,而是MethodCall0,MethodCall1,…,MethodCall5,还有ConstMethodCall0,ConstMethodCall1。这些数字代表什么意思?意思是这个指定方法的入参个数,Const是指入参不能被修改。
本文只以MethodCall0为例进行说明,其余的不过是参数个数不同而已,没有实质区别。当然辅助MethodCall0实现同步的还有另外两个类:ReturnType以及SynchronousMethodCall。
MethodCall0
MethodCall0源码如下所示,该类是一个模板类。
template <typename C, typename R>
class MethodCall0 : public rtc::Message, public rtc::MessageHandler {
public:
typedef R (C::*Method)();
MethodCall0(C* c, Method m) : c_(c), m_(m) {}
R Marshal(const rtc::Location& posted_from, rtc::Thread* t) {
internal::SynchronousMethodCall(this).Invoke(posted_from, t);
return r_.moved_result();
}
private:
void OnMessage(rtc::Message*) { r_.Invoke(c_, m_); }
C* c_;
Method m_;
ReturnType<R> r_;
};
先看一下实际使用中,如何使用MethodCall0。如下源码所示,目标是要实现PeerConnectionFactory类对象pc_factory的指定方法Initialize()在signaling_thread中被同步被执行,当前线程阻塞的获取该方法的执行结果赋值给result。
MethodCall0<PeerConnectionFactory, bool> call(
pc_factory.get(), &PeerConnectionFactory::Initialize);
bool result = call.Marshal(RTC_FROM_HERE, pc_factory->signaling_thread());
对照MethodCall0源码以及实例,可知由模板生成的实际MethodCall0类为以下代码所示。
template <typename PeerConnectionFactory, typename bool>
class MethodCall0 : public rtc::Message, public rtc::MessageHandler {
public:
typedef bool (PeerConnectionFactory::*Initialize)();
MethodCall0(PeerConnectionFactory* c, Initialize m) : c_(c), m_(m) {}
bool Marshal(const rtc::Location& posted_from, rtc::Thread* t) {
internal::SynchronousMethodCall(this).Invoke(posted_from, t);
return r_.moved_result();
}
private:
void OnMessage(rtc::Message*) { r_.Invoke(c_, m_); }
PeerConnectionFactory* c_;
Initialize m_;
ReturnType<bool> r_;
};
那么实际代码就是先创建一个MethodCall0对象call,并调用MethodCall0的Marshal()方法获取并返回结果。那么我们来一步步拆解Marshal()方法:
- 创建SynchronousMethodCall,并将MethodCall0自身作为入参。
- 调用SynchronousMethodCall的Invoke方法,Location对象以及指定线程signaling_thread作为入参。
- 调用MethodCall0对象的私有成员ReturnType<bool> r_的moved_result()获取返回值。
由于第1,2步涉及到一个新的类SynchronousMethodCall,下面看下该类的源码:
SynchronousMethodCall
该类的声明如下:
class SynchronousMethodCall : public rtc::MessageData,
public rtc::MessageHandler {
public:
explicit SynchronousMethodCall(rtc::MessageHandler* proxy);
~SynchronousMethodCall() override;
void Invoke(const rtc::Location& posted_from, rtc::Thread* t);
private:
void OnMessage(rtc::Message*) override;
rtc::Event e_;
rtc::MessageHandler* proxy_;
};
该类的实现如下:
SynchronousMethodCall::SynchronousMethodCall(rtc::MessageHandler* proxy)
: proxy_(proxy) {}
SynchronousMethodCall::~SynchronousMethodCall() = default;
void SynchronousMethodCall::Invoke(const rtc::Location& posted_from,
rtc::Thread* t) {
if (t->IsCurrent()) {
proxy_->OnMessage(nullptr);
} else {
t->Post(posted_from, this, 0);
e_.Wait(rtc::Event::kForever);
}
}
void SynchronousMethodCall::OnMessage(rtc::Message*) {
proxy_->OnMessage(nullptr);
e_.Set();
}
第一步在SynchronousMethodCall构造时,MethodCall0对象作为入参传入,因此SynchronousMethodCall.proxy_就是MethodCall0对象。
第二步调用SynchronousMethodCall.Invoke()方法,传入了指定线程signaling_thread。有两种情况:
- 若执行Invoke()的当前线程就是指定线程signaling_thread,那么直接在当前线程调用MethodCall0.OnMessage()即可,该方法使得指定方法被执行,同时也确保在指定线程上执行的,因为当前线程就是指定线程嘛。且结果被保存在MethodCall0.r_成员中,MethodCall0的OnMessage()的分析且往后放,因为r_为 ReturnType<bool>的实例,涉及到第三个类ReturnType。
- 若Invoke()的当前线程不是指定线程signaling_thread,那么就需要调用指定线程的Post方法,将SynchronousMethodCall作为一个消息投放到指定线程,Post方法不仅投放了消息,并且会唤醒指定线程去处理消息。后续在当前线程上调用Event .Wait()方法使得当前线程阻塞在Event对象上。等待SynchronousMethodCall对象在指定线程线程上被消费,即执行SynchronousMethodCall.OnMessage方法,该方法中执行MethodCall0.OnMessage(),又回到了上面的那种情况,即在指定线程中执行了MethodCall0.OnMessage(),使得指定方法在指定线程上执行。然后Event.Set()将当前线程唤醒,结束阻塞。
饶了很远,其实SynchronousMethodCall.Invoke()的作用就是保证了MethodCall0.OnMessage方法在指定的线程上运行,此时的一切又绕回到MethodCall0类了。看看MethodCall0.OnMessage,为了不走回头路,源码粘贴如下
void OnMessage(rtc::Message*) { r_.Invoke(c_, m_); }
哦,原来就是执行了ReturnType<bool>.Invoke(PeerConnectionFactory, Initialize)方法。那么来看看第三个类ReturnType吧,又是一个模板类~
template <typename R>
class ReturnType {
public:
template <typename C, typename M>
void Invoke(C* c, M m) {
r_ = (c->*m)();
}
template <typename C, typename M, typename T1>
void Invoke(C* c, M m, T1 a1) {
r_ = (c->*m)(std::move(a1));
}
template <typename C, typename M, typename T1, typename T2>
void Invoke(C* c, M m, T1 a1, T2 a2) {
r_ = (c->*m)(std::move(a1), std::move(a2));
}
template <typename C, typename M, typename T1, typename T2, typename T3>
void Invoke(C* c, M m, T1 a1, T2 a2, T3 a3) {
r_ = (c->*m)(std::move(a1), std::move(a2), std::move(a3));
}
template <typename C,
typename M,
typename T1,
typename T2,
typename T3,
typename T4>
void Invoke(C* c, M m, T1 a1, T2 a2, T3 a3, T4 a4) {
r_ = (c->*m)(std::move(a1), std::move(a2), std::move(a3), std::move(a4));
}
template <typename C,
typename M,
typename T1,
typename T2,
typename T3,
typename T4,
typename T5>
void Invoke(C* c, M m, T1 a1, T2 a2, T3 a3, T4 a4, T5 a5) {
r_ = (c->*m)(std::move(a1), std::move(a2), std::move(a3), std::move(a4),
std::move(a5));
}
R moved_result() { return std::move(r_); }
private:
R r_;
};
代码稍微有点长~因为他大爷的目标方法可能的入参有0 1 2 3 4 5个。。所以Invoke方法也有0 1 2 3 4 5共6个。Initialize方法因为没有入参,所以简单,就第一个Invoke了,该方法就是调用了PeerConnectionFactory.Initialize(),并将结果存入成员bool r_中。有木有,一切皆以明了!!
好了,终于可以回到最开头的MethodCall0.Marshal()的第三步了,也很简单,就是使用std::move移动语义将ReturnType持有的结果返回出来。至此,一切明了,我已打出了gg。
return r_.moved_result();
结束了吗?
没,其实在看SynchronousMethodCall部分实现的时候,其作用就是为了确保不论是在哪个线程中调用,MethodCall0.OnMessage方法都能在目标线程中执行。为了实现这点还使用了Thread.Post方法+Event.Wait()。为什么不使用Thread.Send方法或者Thread.Invoke方法直接实现呢?根本就不需要这么绕,甚至SynchronousMethodCall对象都没有存在的必要,如此推敲出来MethodCall0也没有必要存在。。。回到之前那个示例,即WebRTC源码分析-呼叫建立过程之二(创建PeerConnectionFactory)中提过,如果目标线程是没有运行消息循环的线程,那么Thread.Send和Thread.Invoke是无法正常工作的,这正是MethodCall0存在的必要性。然而,分析完MethodCall0发现其依赖了Thread.Post方法来实现其功能,该方法也是在消息循环没有运行时无法正常工作。Oh~God,大型车祸翻车现场。在哪儿翻车的?到此还未理清楚。