OkHttp源码剖析(三) 任务调度器Dispatcher

859 阅读4分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

OkHttp源码剖析(一) 初识okhttp

OkHttp源码剖析(二) 设计模式下的okhttp

OkHttp源码剖析(三) 任务调度器Dispatcher

OkHttp源码剖析(四) 报文读写工具ExchangeCodec

OkHttp源码剖析(五) 代理路由

Dispatcher

OkHttp 对异步请求的线程池主要依赖Dispatcher来实现。

Dispatcher是用于调度后台发起的网络请求的调度器, 有后台总请求数和单主机总请求数的控制。

每个Dispatcher调度器都有一个 ExecutorService, 通过java excutor实现,在内部运行调用,用于真正执行call。

分析一下Dispatcher的几个参数:

  • maxRequests最大请求数:64
  • maxRequestsPerHost 每个 host 最大请求:5
  • readyAsyncCalls双向的等待队列
  • runningAsyncCalls 双向的执行队列
  • promoteAndExecute()将合格的 readyAsyncCalls 升级到 runningAsyncCalls

OkHttp 任务调度器的设计是将请求分别放到了两个队列中,分别是等待队列及执行队列(分同异步),执行队列又分为了同步的执行队列和异步执行队列。

  /** Ready async calls in the order they'll be run. */
  private val readyAsyncCalls = ArrayDeque<AsyncCall>()
​
  /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
  private val runningAsyncCalls = ArrayDeque<AsyncCall>()
​
  /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
  private val runningSyncCalls = ArrayDeque<RealCall>()

先对调度器有个大体认识:OkHttp 的任务调度器Dispatcher,在同步请求来时,将其加入到同步执行队列;在异步请求来时,将其加入异步等待队列后遍历异步等待队列尝试执行异步等待任务。每个请求完成时,通知到 Dispatcher,Dispatcher 再次遍历准备队列尝试执行任务,若没有执行则说明等待队列是空的,此时会调用 idleCallback.run 执行一些空闲时的任务。

同/异步请求下的dispatcher

OkHttp 的执行有两种方式,enqueueexecute,分别代表了异步请求与同步请求。但是无论哪种请求,虽然看上去是call在进行,但实际上都是调度器Dispatcher在进行同/异步操作。

execute

override fun execute(): Response {
    check(executed.compareAndSet(false, true)) { "Already Executed" }
    timeout.enter()
    callStart()
    try {
      client.dispatcher.executed(this)
      return getResponseWithInterceptorChain()
    } finally {
      client.dispatcher.finished(this)
    }
  }

同步方法execute()实际调用的是调度器的 Dispatcher.executed 方法,通知 Dispatcher 要执行该Call ,之后调用getResponseWithInterceptorChain方法获取 Response,不论是否成功都会调用Dispatcher.finished 通知 Dispatcher 该 Call 执行完成。 client.dispatcher.executed(this)方法直接将同步的请求放进了同步执行队列runningSyncCalls当中。

  /** Used by [Call.execute] to signal it is in-flight. */
  @Synchronized internal fun executed(call: RealCall) {
    runningSyncCalls.add(call)
  }

enqueue

如下图所示,异步方法execute()实际调用的是调度器的 Dispatcher.executed 方法。

在用户使用client.newCall(request).enqueue(new Callback())方法后,调用到RealCall的enqueue()方法,enqueue()方法如下:

  override fun enqueue(responseCallback: Callback) {
    check(executed.compareAndSet(false, true)) { "Already Executed" }

    callStart()
    client.dispatcher.enqueue(AsyncCall(responseCallback))
  }

RealCall.enqueue()中调用了dispacher调度器中的enqueue()方法。

  internal fun enqueue(call: AsyncCall) {
    synchronized(this) {
      // 加入等待队列
      readyAsyncCalls.add(call)
      if (!call.call.forWebSocket) {
         // 寻找同一个host的Call
        val existingCall = findExistingCallWithHost(call.host)
         // 复用Call的callsPerHost
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
      }
    }
    promoteAndExecute()
  }

dispacher.enqueue()方法先将call加入到等待队列 readAsyncCalls 中,再调用 findExistingCallWithHost 方法尝试寻找 host 相同的 Call,它的寻找会遍历 readyAsyncCallsrunningAsyncCalls 两个队列。

若找到了对应的 Call,则会调用 call.reuseCallsPerHostFrom 来复用这个 Call 的 callsPerHost,从而便于统计一个 host 对应的 Call 的个数,它是一个 AtomicInteger

之后调用 dispacher.promoteAndExecute() 方法,执行等待队列中的任务:

private fun promoteAndExecute(): Boolean {
    this.assertThreadDoesntHoldLock()

    val executableCalls = mutableListOf<AsyncCall>()
    val isRunning: Boolean
    synchronized(this) {
      //遍历异步等待队列
      val i = readyAsyncCalls.iterator()
      while (i.hasNext()) {
        val asyncCall = i.next()

        if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
        if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.

        i.remove()
        asyncCall.callsPerHost.incrementAndGet()
        executableCalls.add(asyncCall)
        runningAsyncCalls.add(asyncCall)
      }
      isRunning = runningCallsCount() > 0
    }

    for (i in 0 until executableCalls.size) {
      val asyncCall = executableCalls[i]
      asyncCall.executeOn(executorService)
    }

    return isRunning
  }

promoteAndExecute() 方法遍历了 readyAsyncCalls 队列去寻找能够执行的 AsynCall,若找到,会在最后统一调用 AsyncCall.executeOn 方法,在自己的 executorService 线程池中执行该 Call。其中,执行中的任务不能超过 maxRequests

如下代码所示,executorServiceDispatcher.executorService()方法创建,并将创建的线程池返回。

  @get:Synchronized
  @get:JvmName("executorService") val executorService: ExecutorService
    get() {
      if (executorServiceOrNull == null) {
        executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
            SynchronousQueue(), threadFactory("$okHttpName Dispatcher", false))
      }
      return executorServiceOrNull!!
    }

有了线程池,使得asyncCall可以在该线程池所提供的线程中发起 HTTP 请求,获取 Response 并回调 Callback 的对应方法,从而实现任务的调度。 executorService.execute(this)这一行,利用了调度器中的ExecutorServiceAsyncCall进行了execute操作。

   fun executeOn(executorService: ExecutorService) {
      client.dispatcher.assertThreadDoesntHoldLock()

      var success = false
      try {
        // 在对应的ExecutorService中执行该AsyncCall
        executorService.execute(this)
        success = true
      } catch (e: RejectedExecutionException) {
        // 出现问题,调用Callback对应方法
        val ioException = InterruptedIOException("executor rejected")
        ioException.initCause(e)
        noMoreExchanges(ioException)
        responseCallback.onFailure(this@RealCall, ioException)
      } finally {
        // 通知Dispatcher请求完成
        if (!success) {
          client.dispatcher.finished(this) // This call is no longer running!
        }
      }
    }

如下代码所示,可以看出,AsyncCall 是一个 Runnable executorService.execute(this)利用了调度器中的ExecutorServiceAsyncCall进行了execute操作,实际上是执行了AsyncCall这个 Runnable实现的 run 方法,方法中最终调用到了最关键最核心的getResponseWithInterceptorChain()方法。

 override fun run() {threadName("OkHttp ${redactedUrl()}") {
        var signalledCallback = false
        timeout.enter()
        try {
          //// 获取Response
          val response = getResponseWithInterceptorChain()
          signalledCallback = true
          responseCallback.onResponse(this@RealCall, response)
        } catch (e: Exception) {
          ...
        }finally {
          // 通知Dispatcher请求完成
          client.dispatcher.finished(this)
        }
      }
    }

请求完后,不论成功失败,都会调用到 finished() 方法通知 Dispatcher 请求结束:

/** Used by [AsyncCall.run] to signal completion. */
internal fun finished(call: AsyncCall) {
  call.callsPerHost.decrementAndGet()
  finished(runningAsyncCalls, call)
}

这里, finished() 方法调用到了 finished 的另一个重载函数:

  private fun <T> finished(calls: Deque<T>, call: T) {
    val idleCallback: Runnable?
    synchronized(this) {
      if (!calls.remove(call)) throw AssertionError("Call wasn't in-flight!")
      idleCallback = this.idleCallback
    }

    // 尝试执行等待队列中的任务
    val isRunning = promoteAndExecute()

    if (!isRunning && idleCallback != null) {
      idleCallback.run()
    }
  }

finished 再次调用了 promoteAndExecute 方法尝试执行等待队列中的任务,若当前等待队列中没有需要执行的任务,说明目前还比较空闲,没有到达设定的 maxRequests 。此时会调用 idleCallback.run 执行一些空闲 Callback。

参考

square.github.io/okhttp/

zhuanlan.zhihu.com/p/58093669

www.jianshu.com/p/8d69fd920…

juejin.cn/post/684490…