This is the second day of my participation in the August More text Challenge. For details, see:August is more challenging

Next let’s focus on the Okhttp framework asynchronous request process, compared to the synchronous request is a little more complex, let’s continue to learn through the source code

Asynchronous request process source code parsing

As described earlier, there are three steps to asynchronous requests

  • Create the OkHttpClient and Request objects
  • Encapsulate the Request as a Call object
  • The asynchronous request is made by calling the enqueue method of the Call, which is then handled in onResponse and onFailure in the Callback

Asynchronous Request Process

1. The preceding steps are consistent with the synchronization request process

See the previous article (OkHttp Network Framework Parsing (Part 1) for more details.

The most important thing is to Call the Call enqueue method to schedule the actual Http request.

We can focus on the implementation of the enqueue method:

  void enqueue(Callback responseCallback);
Copy the code

Since this enqueue is an interface, we need to look at how it is implemented in RealCall:

@Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }
Copy the code

“Synchronized” means “synchronized” and “synchronized” means “synchronized”. “Synchronized” means “synchronized” and “synchronized” means “synchronized”. This call object can only be executed once. This call object can only be executed once. This call object can only be executed once. And then we’re going to look at the last line of code, which encapsulates AsyncCall with the responseCallback object that we pass in, but we don’t know what it does, right

3. Let’s look at the implementation of AsyncCall

 final class AsyncCall extends NamedRunnable {... }Copy the code

This class inherits NammedRunnable. If you want to add a Runnable AsyncCall class, you might want to add a Runnable AsyncCall class. If you want to add an AsyncCall class, you might want to add a Runnable AsyncCall class. The client.Dispatcher dispatcher class is called, and then the enqueue method is called to complete the actual asynchronous request.

4. Look at the Dispatcher implementation

  public Dispatcher dispatcher(a) {
    return dispatcher;
  }
Copy the code

This is simply returning a dispatcher object, so the question is, where is the dispatcher assigned?

  • Reviewing the previous synchronization request resolution, you can see that the Dispatcher() object is instantiated in the constructor of ** okHttpClient.Builder ()**

5. Call dispatcher().enqueue method implementation

When we instantiate the Dispatcher object, we can call its enqueue method as follows:

 synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else{ readyAsyncCalls.add(call); }}Copy the code
  • We first see that the method header uses the synchronized keyword, adds a lock, and passes in a Runnable implementation class AsyncCall object
  • We then determine whether the number of runningAsyncCalls is less than the maximum number of requests, and whether the number of requests per host running is less than the maximum number of requests we set for the host. If this is true, we add the incoming AsyncCall to the queue we are currently running. Requests are made through a pool of threads, and if they are not met they are queued up for cached requests
  • Again, runningAsyncCall is a queue that determines the number of concurrent requests

// Cache the waiting request queue
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

  /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
Copy the code

Summary of Enqueue method

  • Determine the current Call(whether the actual HTTP request was executed only once)
  • Encapsulated as an AsyncCall object
  • Call client.dispatcher().enqueue and add it to the queue that we are running, not to the queue that the cache is waiting for

Thread pools in asynchronous requests

The AsyncCall is added to the running request queue if the conditions are met. Once this is done, we call the thread pool to execute the request

1. Look at the implementation of the executorService() method

 public synchronized ExecutorService executorService(a) {
    if (executorService == null) {
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher".false));
    }
    return executorService;
  }
Copy the code
  • Seeing that this method uses the synchronized keyword to ensure that the entire thread pool is a singleton, the method returns an EXecutorService() thread pool object.
  • Instantiate the thread pool object ThreadPoolExecutor, passing two parameters, one corePoolSize set to 0 and the other maximum number of thread pools set to the maximum value of Integer
  • The question is, if there are many, many thread pools, will the overall performance cost be too high? The answer is no. The Dispatcher defines a maxRequest value that limits the number of okHTTP asynchronous requests, so setting an integer maximum in the thread pool does not affect this behavior.

Call the excute() method

Once the thread pool is created, the exCute () method is called, and since this is a thread pool, it calls the Run method of each child thread, which calls the Run method of AsyncCall

  • AysncCall inherits NamedRunnable, and AysncCall inherits NamedRunnable.
public abstract class NamedRunnable implements Runnable {
  protected final String name;

  public NamedRunnable(String format, Object... args) {
    this.name = Util.format(format, args);
  }

  @Override public final void run(a) {
    String oldName = Thread.currentThread().getName();
    Thread.currentThread().setName(name);
    try {
      execute();
    } finally{ Thread.currentThread().setName(oldName); }}protected abstract void execute(a);
}

Copy the code

As you can see, the Run method here is really just wrapped up, not really doing anything, mostly in the execute() method, but excute() is an abstract class here, so we need to find its exCute () implementation in its RealCall, okay

  • In AsyncCall RealCall, the execute() method is where the real action takes place. Let’s take a look at this
@Override protected void execute(a) {
      boolean signalledCallback = false;
      try {
        Response response = getResponseWithInterceptorChain();
        if (retryAndFollowUpInterceptor.isCanceled()) {
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this.new IOException("Canceled"));
        } else {
          signalledCallback = true;
          responseCallback.onResponse(RealCall.this, response); }}catch (IOException e) {
        if (signalledCallback) {
          // Do not signal the callback twice!
          Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          eventListener.callFailed(RealCall.this, e);
          responseCallback.onFailure(RealCall.this, e); }}finally {
        client.dispatcher().finished(this); }}Copy the code
  1. We first build the chain of interceptors, which we’ll focus on later, and we return a Response object with this method
  2. It then determines if the interceptor (redirection and retry interceptors) is cancelled, and if it is cancelled onFailure() is called, and if it is not, onResponse() is called. Needless to say, this is all done in the child thread, as described earlier.
  3. In the catch module, some operations of network failure are performed and related logs are printed
  4. Last but not least, the finish() method is called
  • We can look at the finished() method
 void finished(AsyncCall call) {
    finished(runningAsyncCalls, call, true);
  }
Copy the code

The finished method has been called with three arguments.

private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
    int runningCallsCount;
    Runnable idleCallback;
    synchronized (this) {
      if(! calls.remove(call))throw new AssertionError("Call wasn't in-flight!");
      if (promoteCalls) promoteCalls();
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }

    if (runningCallsCount == 0&& idleCallback ! =null) { idleCallback.run(); }}Copy the code

Just to take a quick look, as I understand it, Finish does three things here

  1. The first thing to do to remove the request from the queue that is being requested is the **calls.remove()** method
  2. The second thing is that promoteCalls are called, a method that adjusts the task queue for the entire asynchronous request
  3. The third thing you need to do is recalculate the number of threads executing and assign a value to them, which will be determined later

Asynchronous summary

  1. Create an OkhttpClient object
  2. Build a Request object (Builder mode), through OkHttpClient and Request object, build a Call object
  3. Execute the call’s enqueue method to add the entire HTTP request to the scheduler. Since it is asynchronous, a Callback object needs to be injected to distribute the network request.

Ps: For asynchronous requests, its Dispatcher needs to pay special attention. It has two queues, one is used to execute the task, one is waiting to execute the queue, and one thread pool, these three to complete the asynchronous network request

Stern said

In my opinion, whether asynchronous request or synchronous request, the real network request is still operated through the interceptor chain. After analyzing this, we actually found that Okhttp has many cores, but the two most important task dispatching core classes are dispatcher and interceptor chain. Welcome to learn together with me and continue the journey.

To be continued