OkHttp源码学习之Dispatcher

334 阅读3分钟

Dispatcher

Dispatcher 是一个用于调度网络请求的组件,它负责将请求分发到不同的线程或线程池中执行。

Dispatcher 的主要作用是:

线程管理:Dispatcher 负责管理网络请求的线程,它可以将请求分发到不同的线程池中执行,以实现并发处理多个请求。

任务调度:Dispatcher 可以根据请求的优先级、类型或者其他的调度策略,决定在哪个线程上执行网络请求。

线程隔离:通过使用不同的 Dispatcher,OKHTTP 可以确保网络请求在特定的线程上执行,避免阻塞主线程,提高应用的响应性和性能。

并发控制:Dispatcher 可以控制同时执行的网络请求数量,避免过多请求导致系统资源耗尽。

在OKHTTP中,Dispatcher 通常与 Dispatcher.Main、Dispatcher.IO、Dispatcher.Default 等预定义的调度器一起使用,这些调度器提供了不同的线程池,用于执行不同的任务。例如,Dispatcher.IO 通常用于执行后台的网络请求,而 Dispatcher.Main 用于执行与UI相关的操作。

最大并发执行请求数 64

每个域名并发执行的最大请求数 5

下面看一下最重要的几个方法以及配置变量。

class Dispatcher() {
    
   //最大并发执行请求数
    var maxRequests = 64
      get() = this.withLock { field }
      set(maxRequests) {
        require(maxRequests >= 1) { "max < 1: $maxRequests" }
        this.withLock {
          field = maxRequests
        }
        promoteAndExecute() 
      }

    //每个域名并发执行的最大请求数
    var maxRequestsPerHost = 5
      get() = this.withLock { field }
      set(maxRequestsPerHost) {
        require(maxRequestsPerHost >= 1) { "max < 1: $maxRequestsPerHost" }
        this.withLock {
          field = maxRequestsPerHost
        }
        promoteAndExecute()
      }
    
    var idleCallback: Runnable? = null  //每次调度程序空闲时(当正在运行的调用数量返回零时)调用的回调。
    
    private val readyAsyncCalls = ArrayDeque<AsyncCall>()
    private val runningAsyncCalls = ArrayDeque<AsyncCall>()
    private val runningSyncCalls = ArrayDeque<RealCall>()

}

可以看到 promoteAndExecute() 是出现频率最高的一方法,特别是在set()方法之后,那 promoteAndExecute() 里面到底干了什么呢

//将符合条件的调用从readyAsyncCalls提升到runningAsyncCalls,并在执行程序服务上运行它们
//上面的所有成员变量以及对象几乎都会在下面的方法中被调用
//主要是使用ArrayDeque负责每个call的ready-running状态的切换,
private fun promoteAndExecute(): Boolean {
  lock.assertNotHeld()  // 断言当前线程没有持有锁

  val executableCalls = mutableListOf<AsyncCall>() // 创建一个可执行的异步调用列表
  var isRunning: Boolean // 标记是否还有正在运行的调用
  this.withLock { // 获取锁,确保线程安全
    val i = readyAsyncCalls.iterator() // 获取readyAsyncCalls的迭代器
    while (i.hasNext()) { // 遍历readyAsyncCalls
      val asyncCall = i.next() // 获取下一个异步调用

    if (runningAsyncCalls.size >= this.maxRequests) break // 如果当前运行的异步调用数量达到最大限制,则跳出循环
      if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // 如果当前主机已经达到最大请求限制,则跳过该调用

      i.remove() // 从readyAsyncCalls中移除该调用
      asyncCall.callsPerHost.incrementAndGet() // 增加该主机调用的计数
      executableCalls.add(asyncCall) // 将调用添加到executableCalls列表
      runningAsyncCalls.add(asyncCall) // 将调用添加到runningAsyncCalls列表
    }
    isRunning = runningCallsCount() > 0 // 标记是否有正在运行的调用
  }

  // 如果executorService已经被关闭,则取消执行并减少计数
  if (executorService.isShutdown) {
    for (i in 0 until executableCalls.size) {
      val asyncCall = executableCalls[i]
      asyncCall.callsPerHost.decrementAndGet()

      this.withLock {
        runningAsyncCalls.remove(asyncCall)
      }

      asyncCall.failRejected() // 调用失败处理
    }
    idleCallback?.run() // 如果存在空闲回调,则执行
  } else {
    for (i in 0 until executableCalls.size) {
      val asyncCall = executableCalls[i]
      asyncCall.executeOn(executorService)
    }
  }

  return isRunning  // 返回是否有正在运行的调用
}

首先,它获取一个锁,以确保在多线程环境中操作队列时的线程安全。

然后,它遍历readyAsyncCalls队列,选择可以执行的调用(即当前运行的异步调用数量没有达到最大限制,且每个主机的调用数量也没有达到最大限制)。

如果executorService已经关闭,它会取消这些调用并减少计数,然后执行相应的失败处理。

如果executorService没有关闭,它会执行这些调用。

最后,它返回一个布尔值,表示是否有正在运行的调用。

使用

val client = OkHttpClient.Builder()
    .dispatcher(Dispatcher().apply {
        maxRequests = 32 // 设置同时执行的最大请求数量
        maxRequestsPerHost = 5 // 设置每个主机的最大请求数量
    })
    .build()