自作ソフトを並列処理にしてみた

投稿者: | 2021年4月17日

研究室のオンラインミーティング自動開催ツールを弄る回です。

完成当時の実装ですと、RPAツールが例外を飛ばして落ちた瞬間にGUIも落ちるので、これらをスレッドとして分割してRPAツールがGUIに影響を与えないようにします。

1.修正後のコード

import PySimpleGUI as sg
import datetime as dt
import getschedule as gs
import operate_zoom as oz
import time
import threading
sg.theme('DarkBrown1')
layout = [
[sg.Text('Next', size = (5,1), justification = 'left'), sg.Text(size = (20,1), key='-START-'),sg.Text('~',size = (6,1)), sg.Text(size=(20,1), key='-END-')],
[sg.Text('Now',size = (5,1),justification = 'left'),sg.Text(size=(20,1), key = '-NOW-')],
[sg.Text('Log', size =(5,1), justification = 'left')],
[sg.Output(size=(100,10), key='-LOG-')],
[sg.Button('Start/Stop', focus=True), sg.Quit()]
]
window = sg.Window('ミーティング開催', layout)
count = -1 #初回ループ時に予定取得させるため
def start_zoom(now_date):
oz.Start_Zoom()
print('{} Start meeting'.format(now_date))
#タイミング調整
time.sleep(5)
#画面共有開始
oz.screen_sharing()
def end_zoom(now_date):
global count
zoom_flag = True
oz.Stop_Zoom()
print('{} Quit meeting'.format(now_date))
#ミーティング終了後、即座に予定を再取得する
count = -1
def GUI():
global count
RPA_running = False
next_start_date, next_end_date = '0000-00-00 00:00', '0000-00-00 00:00'
interval = 0 #ミーティング開始時、終了時から1分間カウントする変数
zoom_flag = False #何度も起動するのを防ぐため。Trueの場合1分経過でFalseに戻す。
while True:
event, values = window.read(timeout=1000) #ループ間隔は1秒
#現在時刻を取得
now = dt.datetime.now()
#時刻表示のために整形
now_time = now.strftime('%Y-%m-%d %H:%M:%S')
#比較のためにstr型に変換
now_date = now.strftime('%Y-%m-%d %H:%M')
#現在時刻の表示
window['-NOW-'].update(now_time)
#ボタン操作
if event in ('Quit'):
break
elif event == 'Start/Stop':
RPA_running = not RPA_running
if RPA_running:
print('{} Start'.format(now_date))
else:
print('{} Stop'.format(now_date))
#再開時に予定を取得させるため
count = -1
if RPA_running:
#30分間隔で予定取得
if count == -1 or count == 1800:
#カウンタを戻す
count = 0
#予定を5つ取得し、直近の予定の開始時刻と終了時刻を格納
schedule = gs.get_events()
start_date = schedule[0]
end_date = schedule[1]
#予定を整形
start_date = start_date[0:10] + ' ' + start_date[11:16]
end_date = end_date[0:10] + ' ' + end_date[11:16]
#予定を取得したらログに記述
print('{} Get next schedule'.format(now_date))
#次の予定が変更されたらログに記述
if start_date != next_start_date:
next_start_date = start_date
next_end_date = end_date
print('{} Update next schedule'.format(now_date))
#GUI表示
window['-START-'].update(start_date)
window['-END-'].update(end_date)
#ミーティング開始時、終了時にカウントを始める
if zoom_flag == True:
interval += 1
#60秒経過したらフラッグを反転させ、変数を初期値に戻す
if interval == 60:
zoom_flag = not zoom_flag
interval = 0
#開始時刻かつ直前にミーティングを起動していないならミーティングを開始する
if start_date == now_date and zoom_flag == False:
zoom_flag = True
zoom_thread = threading.Thread(target=start_zoom, daemon=True, args=(now_date,))
zoom_thread.start()
#終了時刻にかつ直前にミーティングを終了していないならミーティングを終了する
if end_date == now_date and zoom_flag == False:
zoom_flag = True
zoom_thread = threading.Thread(target = end_zoom, daemon = True, args = (now_date,))
zoom_thread.start()
#カウンタを増やす
count += 1
else:
continue
window.close()
if __name__ == '__main__':
GUI_thread = threading.Thread(target=GUI)
GUI_thread.start()

2.Pythonの並列処理

threadingというモジュールが用意されています。これを用いると容易に実装できます。

他にもスレッドプールやプロセスルールなどもあります。

今回は、GUIを走らせるスレッドとRPAツールを走らせるスレッドを分けるだけのため、スレッドプールのように同時に動かす最大数を制限する必要はなく、プロセスとして分割してマルチコアで動かす必要もないため、threadingで並列処理を行いました。

3.並列処理の前に

もともとRPAツール部分はGUI内のwhile文内に書かれていました。しかし、threadingで並列処理する場合は、実行する関数とともにTreadクラスをインスタンス化する必要があります。要するに、今回の変更ではRPAツールを関数化する必要がありました。

4.この変数ってグローバル変数の必要ある?

RPAツールの関数化とともに、グローバル環境で宣言している変数についても見直しを行いました。グローバル環境にすべて変数を宣言するのはお行儀が良くないので、できる限りその変数を使う関数内に移動させました。

5.Pythonのグローバル変数を関数内で使う時

グローバル変数とローカル変数に同じ名前をつけることができます。また、関数内で宣言されるとローカル変数と扱われます。関数内でグローバル関数を使う場合は、変数名の前にglobalをつけます。

6.startするのを忘れない

インスタンス化した後、startするのを忘れると意味がないので忘れないようにしましょう。

7.おわりに

調べてすぐにマルチスレッド化できてびっくりしました。でもマルチスレッド化ならC++も比較的簡単だった覚えがあります。

実装予定の機能ができたので、次はログをファイルに出力するようにしたいです。ロギング機能ってのがあるらしい。

8.参考文献

Pythonの並列・並行処理サンプルコードまとめ

threading —- スレッドベースの並列処理

threading – スレッドによる並列処理を管理する

関数の中でグローバル変数を代入する

Python♪関数で変なエラーが出た:local variable ‘x’ referenced before assignment

コメントを残す

This site uses Akismet to reduce spam. Learn how your comment data is processed.