Concurrency patterns – Half-Sync / Half-Async

The pattern separates asynchronous I/O from the synchronous one. Main thread doesn’t block on incoming client requests and long-running operations are offloaded to a dedicated synchronous layer. Processing results are delivered by the means of callbacks.

Application

  • largely used in operating systems (hardware interrupts, application management)
  • Android – AsyncTask (file downloads ..)

A decent queuing system is required to handle messaging between the two layers. The challenge lies in preventing race conditions and other concurrency related issues.

Key Components

    • Synchronous Service Layer: deals with long-running tasks, implements the core business logic
  • Queuing Layer: a request queue, doesn’t block the caller
  • Asynchronous Service Layer: dispatches incoming requests to the queue
There might be a number of concurrently running synchronous services. The queueing layer is responsible for thread synchronization.

 

Advantages

    • Reduced code complexity: Synchronized services focus solely on the core logic.
  • Separation of concerns: Each of the layers is relatively self-contained and serves a different purpose.
  • Centralized communication: The queuing layer mediates all communication – no moving parts flying around

Drawbacks

    • Performance overhead: “Boundary-crossing penalty” – context switching, synchronization, data transfer ..
  • Harder to debug: Asynchronous callbacks make testing and debugging less straightforward
  • Benefit questionable: Higher-level application services may not benefit from asynchronous I/O. That depends on framework / OS design.

Example

An ASCII Art generator (credit goes to Evilzone) is not only pleasant to work with, but it is also a suitable candidate for a long-running task. I saw it as a perfect fit for the pattern.
public class AsciiArtGenerator {
..
  /**
  * Converts an image to its ASCII representation.
  *
  * @param imgPath  path to the image, relative to /src/main/resources
  * @param outPath  path to the resulting text file, relative to ./data
  * @return true, if the conversion succeeds, false otherwise
  */
  public boolean convertToAscii(String imgPath, String outPath) {..}
}
The image-to-text conversion is a synchronous blocking task, which might take a while to complete. As such it’s bound to run in a background thread.

The front-end of the app is served asynchronously via a non-blocking dispatcher:
/**
 * Represents an asynchronous layer, as it forwards
 * client requests for further processing and returns
 * immediately. It receives results via notifications.
 *
 * @author: Tomas Zezula
 * Date: 24/08/2014
 */
public class NonBlockingDispatcher {
  ..
  /**
   * Sends a request to the queue and returns instantly.
   *
   * @param imgPath  Image path for the ASCII generator
   * @param outPath  Output path for the ASCII generator
   */
  public void dispatch(final String imgPath, final String outPath) {..}  

  /**
   * Captures processing result and notifies the subscribed client
   *
   * @param result true, if success, false otherwise
   */
  public void onResult(boolean result) {..}
}

Finally, the communication between the dispatcher and the worker thread is mediated by a dedicated queuing channel:

/**
 * Queues incoming requests and notifies the dispatcher when the response is ready.
 *
 * @author: Tomas Zezula
 * Date: 24/08/2014
 */
public class WorkQueue {..}


As usual, the example is accompanied by unit tests proving the core concepts. This time, I kept the tests to a bare minimum, just to highlight the major difference between a naive single-threaded synchronous approach and the slightly more advanced asynchronous implementation.


The app pays tribute to a great actress of the last century. The resulting file (./data/audrey.txt) is best viewed using a minimal font-size.

Source Code

Resources



Similar Posts