PySimpleGUIとmultithreadを使った例

##はじめに
PySimpleGUIが使いやすいので、以前使っていたLabVIEWのデザインパターンの一つ「キューメッセージハンドラデザインパターン」を実現できないか取り組んだ結果です。

LabVIEWキューメッセージハンドラの説明
[アプリケーションデザインパターン:生産者/消費者](http://www.ni.com/tutorial/3023/ja/)
[【LabVIEW スキルアップ】デザインパターン”QMH”とは?](https://forums.ni.com/t5/LabVIEW-Caf%C3%A9/LabVIEW-%E3%82%B9%E3%82%AD%E3%83%AB%E3%82%A2%E3%83%83%E3%83%97-%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3-QMH-%E3%81%A8%E3%81%AF/gpm-p/3985209?profile.language=ja)

LabVIEWについては[Wiki](https://ja.wikipedia.org/wiki/LabVIEW)などを見ていただければと思いますが、計測装置の制御プログラムでよく使われています。（最近はPythonに押されていますが。）
特徴は、グラフィカルプログラミングで、whileループを一つ書けば、1つのコアまたはスレッドを使う。なので、Whileループをいっぱい書けばマルチタスクになります。キューメッセージハンドラデザインパターンはWhileループ同士のデータのやり取りをキューで行うものです。

##環境
前回の記事 [PySimpleGUIでの時刻表示（timeoutの使い方）](https://qiita.com/Gyutan/items/2c61159a19073b19012b)と同じです。

Win10Pro
Anaconda
Python3.7

##やりたいこと
PySimpleGUIは、イベント（ボタンを押すとか、入力するとかのイベント）待機と、処理されたデータのUIへの表示を、受け持ちます。マルチスレッドを使って、受け取ったイベントを処理する関数へ、キューでデータを送ります。受け取ったデータをもとに関数は処理を行い、結果をキューで返します。
スレッド間通信（プロセス間通信）では、グローバル変数も使うことができますが、今回はキューを使っています。
図に書くと以下のようなものです。
![simplegui_que.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165987/0ace98b2-1e10-3a2c-a726-a8a2b7f36819.jpeg)

参考にしたデモプログラムは、[Demo_Multithreaded_Multiple_Threads](https://github.com/PySimpleGUI/PySimpleGUI/blob/master/DemoPrograms/Demo_Multithreaded_Multiple_Threads.py)です。
ちなみに、[Demo_Multithreaded_Long_Task_Simple](https://github.com/PySimpleGUI/PySimpleGUI/blob/master/DemoPrograms/Demo_Multithreaded_Long_Task_Simple.py)はグローバル変数を使ったものです。

##作成したコード
```python

from queue import Queue
from threading import Thread
import time

import PySimpleGUI as sg

ui_que = Queue()
data_que = Queue()

def show_time():
    jikan = time.strftime('%p %I:%M:%S')
    return jikan

def worker(data_que,ui_que):
    result = data_que.get()
    time.sleep(1)
    ui_que.put(result[0])

def worker_do(data_que,ui_que):
    result_do = data_que.get()
    time.sleep(0.5)
    ui_que.put(len(result_do))


sg.theme('Dark')
layout = [
          [sg.Text(size=(15, 1), font=('Helvetica', 20),justification='center', key='-jikan-')],
          [sg.Text('Data', size=(8, 1)),sg.Input(key='-data-')],
          [sg.Submit()],
          [sg.Text('Data2', size=(8, 1)),sg.Input(key='-data2-')],
          [sg.Button('DO',key='-do-'), sg.Cancel()],
          [sg.Text('Status:', size=(8, 1)),sg.Text('',size=(8,1), key='-status-')],
          [sg.Output(size=(70, 2))]
          ]

window = sg.Window('template',layout)

while True:
    event, values = window.read(timeout=100,timeout_key='-timeout-')
    # event, values = window.read()
    #timeoutを指定することで、timeoutイベントが起こります。timeoutの単位ms
    # print(event,values)
    #↑コメントアウトを外すと、どんなイベントが起こっているか確かめることができます。
    if event in (None,'Cancel'):
        break
    elif event in 'Submit':
        print('Input data: {}'.format(values['-data-']))
        # デーモンスレッドはデーモンスレッド以外のスレッドがなくなった時点で全て自動終了する
        thread = Thread(target=worker, args=(data_que,ui_que), daemon=True).start()
        data_que.put(values['-data-'])
        
    elif event in '-do-':
        print('do somthing: {}'.format(values['-data2-']))
        thread_do = Thread(target=worker_do, args=(data_que,ui_que), daemon=True).start()
        data_que.put(values['-data2-'])
        
    elif event in '-timeout-':
        jikan = show_time()
        window['-jikan-'].update(jikan)
        
        try:
            ui_data=ui_que.get_nowait()
        except :
            ui_data = None
        if ui_data:
            print(ui_data)   
            window['-status-'].update(ui_data)
    

window.close()
```
##実行例とコードの解説
![psg_que.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/165987/297b6436-9e8a-1989-8f34-67d127e4cde4.jpeg)

`Data`に数字や文字を入れて`Submit`を押すと1番最初の文字が返ってきます。
同じように`Data2`に数字や文字を入れて`Do`を押す入力した文字列の長さが返ってきます。


`layout`の中にある`sg.Output`はコードの中の`print`で書いたところが出力されます。`submit`ボタンが押されると、`worker1`のスレッドが立ち上がり、`data`を`data_que`で送ります。`worker1`で処理して、`ui_que`で結果を送ります。
`do`ボタンも同様の処理を行います。
前回の記事にもあるように`window.read`の`timeout`を利用して状態監視を行います。
`timeout`は`elif`をつかって明示的に書いています。（Pyhtonのポリシーとして明示的に書いた方が良いと思ったので。）
`ui_que.get_nowait()`では、何も情報が入っていない時には、エラーを出します。エラーを受け取るために`try-except`を使っています。

##まとめ
LabVIEWでよく使っていたキューメッセージハンドラデザインパターに似たものをPySimPleGUIで作ることができました。
もう少し使いやすくするために改良していこうかとも思います。