Sunday, 31 August 2014

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



No comments:

Post a Comment