Chapter 3. Asynchronous Programming and Networking

Several business applications need to react to external stimuli—such as network traffic—asynchronously. An example of such software might be a desktop application that allows us to track a company's share prices in the stock market.

We will build this application first using a more traditional approach. In doing so, we will:

  • Be able to identify and understand the drawbacks of the first design
  • Learn how to use RxClojure to deal with stateful computations such as rolling averages
  • Rewrite the example in a declarative fashion using observable sequences, thus reducing the complexity found in our first approach

Building a stock market monitoring application

Our stock market program will consist of three main components:

  • A function simulating an external service from which we can query the current price—this would likely be a network call in a real setting
  • A scheduler that polls the preceding function at a predefined interval
  • A display function responsible for updating the screen

We'll start by creating a new leiningen project, where the source code for our application will live. Type the following on the command line and then switch into the newly created directory:

lein new stock-market-monitor
cd stock-market-monitor

As we'll be building a GUI for this application, go ahead and add a dependency on Seesaw to the dependencies section of your project.clj:

[seesaw "1.4.4"]

Next, create a src/stock_market_monitor/core.clj file in your favorite editor. Let's create and configure our application's UI components:

(ns stock-market-monitor.core
  (:require [seesaw.core :refer :all])
  (:import (java.util.concurrent ScheduledThreadPoolExecutor
                                 TimeUnit)))

(native!)

(def main-frame (frame :title "Stock price monitor"
                       :width 200 :height 100
                       :on-close :exit))

(def price-label       (label "Price: -"))

(config! main-frame :content price-label)

As you can see, the UI is fairly simple. It consists of a single label that will display a company's share price. We also imported two Java classes, ScheduledThreadPoolExecutor and TimeUnit, which we will use shortly.

The next thing we need is our polling machinery so that we can invoke the pricing service on a given schedule. We'll implement this via a thread pool so as not to block the main thread:

Tip

User interface SDKs such as swing have the concept of a main—or UI—thread. This is the thread used by the SDK to render the UI components to the screen. As such, if we have blocking—or even simply slow running— operations execute in this thread, the user experience will be severely affected, hence the use of a thread pool to offload expensive function calls.

(def pool (atom nil))

(defn init-scheduler [num-threads]
  (reset! pool  (ScheduledThreadPoolExecutor. num-threads)))
(defn run-every [pool millis f]
  (.scheduleWithFixedDelay pool
                           f
                           0 millis TimeUnit/MILLISECONDS))

(defn shutdown [pool]
  (println "Shutting down scheduler...")
  (.shutdown pool))

The init-scheduler function creates ScheduledThreadPoolExecutor with the given number of threads. That's the thread pool in which our periodic function will run. The run-every function schedules a function f in the given pool to run at the interval specified by millis. Finally, shutdown is a function that will be called on program termination and shutdown the thread pool gracefully.

The rest of the program puts all these parts together:

(defn share-price [company-code]
  (Thread/sleep 200)
  (rand-int 1000))


(defn -main [& args]
  (show! main-frame)
  (.addShutdownHook (Runtime/getRuntime)
                    (Thread. #(shutdown @pool)))
  (init-scheduler 1)
  (run-every @pool 500
             #(->> (str "Price: " (share-price "XYZ"))
                   (text! price-label)
                   invoke-now)))

The share-price function sleeps for 200 milliseconds to simulate network latency and returns a random integer between 0 and 1,000 representing the stock's price.

The first line of our -main function adds a shutdown hook to the runtime. This allows our program to intercept termination—such as pressing Ctrl + C in a terminal window—and gives us the opportunity to shutdown the thread pool.

Tip

The ScheduledThreadPoolExecutor pool creates non-daemon threads by default. A program cannot terminate if there are any non-daemon threads alive in addition to the program's main thread. This is why the shutdown hook is necessary.

Next, we initialize the scheduler with a single thread and schedule a function to be executed every 500 milliseconds. This function asks the share-price function for XYZ's current price and updates the label.

Tip

Desktop applications require all rendering to be done in the UI thread. However, our periodic function runs on a separate thread and needs to update the price label. This is why we use invoke-now, which is a Seesaw function that schedules its body to be executed in the UI thread as soon as possible.

Let's run the program by typing the following command in the project's root directory:

lein trampoline run -m stock-market-monitor.core

Tip

Trampolining tells leiningen not to nest our program's JVM within its own, thus freeing us to handle uses of Ctrl + C ourselves through shutdown hooks.

A window like the one shown in the following screenshot will be displayed, with the values on it being updated as per the schedule implemented earlier:

Building a stock market monitoring application

This is a fine solution. The code is relatively straightforward and satisfies our original requirements. However, if we look at the big picture, there is a fair bit of noise in our program. Most of its lines of code are dealing with creating and managing a thread pool, which, while necessary, isn't central to the problem we're solving—it's an implementation detail.

We'll keep things as they are for the moment and add a new requirement: rolling averages.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.172.200