官术网_书友最值得收藏!

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 that's 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.5.0"] 

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:

(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)) 
User interface SDKs such as Swing have the concept of a main—or UI—thread. This is the thread that's used by the SDK to render the UI components to the screen. As such, if we have blocked,   or even simply slow-running, operations executing in this thread, the user's experience will be severely affected, hence the use of a thread pool to offload expensive function calls.

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 thus will shut down the thread pool gracefully.

The rest of the program puts all of 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 second 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 shut down the thread pool.

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.

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  
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 that we implemented earlier:

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.

主站蜘蛛池模板: 定结县| 巧家县| 重庆市| 临高县| 尤溪县| 凤冈县| 泾川县| 交口县| 兴山县| 昂仁县| 阿克| 龙游县| 屏东县| 偏关县| 临邑县| 旺苍县| 沽源县| 曲阳县| 曲阜市| 赫章县| 乌鲁木齐县| 虞城县| 鄂托克旗| 唐山市| 宁武县| 聂拉木县| 固原市| 射阳县| 高碑店市| 亚东县| 商水县| 上犹县| 琼结县| 巧家县| 榕江县| 梅河口市| 平定县| 乡城县| 万年县| 寻甸| 广州市|