WebRTC源码分析-线程基础之跨线程同步MethodCall

前言

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()方法:

  1. 创建SynchronousMethodCall,并将MethodCall0自身作为入参。
  2. 调用SynchronousMethodCall的Invoke方法,Location对象以及指定线程signaling_thread作为入参。
  3. 调用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,大型车祸翻车现场。在哪儿翻车的?到此还未理清楚。

你可能感兴趣的:(WebRTC源码分析,WebRTC源码分析)