ASIO Services Management

实践中遇到的问题

之前,我们开发的应用也是基于异步服务的,在应用中存在大量命名为 xxx_service 的单例让用户使用,至少存在两个问题:

ASIO 的解决方案

我们开发的应用的模式看起来跟 ASIO 是非常相似的,都是基于异步服务进行交互的。

ASIO 引入了一个 execution_context 来统一管理各种 xxx_service,这个 execution_context 可以是单例(e.g. system_executor_impl),也可以是个在寄宿在 main 函数内的对象(e.g. io_context),这看起来跟一般 Qt 应用,需要一个 QApplication 是一样的。当应用退出前,便会虚构这个对象,值此之际便可以处理管理的所有服务。

我们之前使用的各个 xxx_service 单例,在 ASIO 中如何拿到实例呢?

ASIO 使用按需创建的方式来获取,使用 use_service<T>(),既可以按需创建服务实例并注册到 execution_context,也可以检索到寄宿在 execution_context 上服务。

ASIO 服务管理的结构如下所示:

execution_context

异构应用的问题

由于我们的库应用在不同的端上,既可以应用在服务端,也可以应用在客户端。这可能会遇到一个问题,比如定义的 xxx_service 在不同端上的行为是不同的,这时就会分化成 xxx_service_for_clientxxx_serivice_for_server 来实现各自的行为。

对于使用这个服务的其它 other_service 不应该察觉到该变化是最佳的,比如仍然使用 use_service<xxx_service> 来取得该服务。ASIO 使用了模板元编程最基本的方式 Traits 来解决该问题。

Traits: class templates that have a nested type alias called (by convention) type.

template <class _Service>
struct service_void_type
{
  typedef void _Type;
};

template <class _Service, class = void>
struct service_key_type
{
  typedef _Service _Type;
};

template <class _Service>
struct service_key_type<_Service,
  typename service_void_type<typename _Service::key_type>::_Type>
{
  typedef typename _Service::key_type _Type;
};

这样,只需要在类内嵌入一个 using key_type = xxx_service, 以下形式得到结果都是一样的:

other_service 不需要做任何变化,依然使用 use_service<xxx_service>() 来获取服务,但是有一点需要注意的是要确保在 other_service 之前注册 xxx_service,那怎么做呢:

所以,在这种情况下,不应让用户知道 xxx_service_for_clientxxx_service_for_server 的存在,这个工作由实现者来处理,比如把它放到私有命名空间内,让用户意识到不能直接使用这些类。

其它

在 asio 里面可以发现一些不带数据的服务也注册到 execution_context 中,这有两个好处:

避免数据竞争 如下所示,actor 使用 A, A 使用 BCD。如果 A 没有注册,则每实例化一次,将多次调用 use_service<A, B, C, D>()。而如果 A 注册了,只需要访问 use_service<A>() 一次,而 BCD 则缓存在 A 内部。

              |--> B
actor --> A --|--> C
              |--> D

解决异构问题 如前面所说,actor 则不需要关心 A 在不同端有不同实现时的差异。