tkinter: afterメソッドを1ミリ秒以下にできたが、機序が分からない
問題となっている現象
時計の針を高速回転させて、残像ができないか試行錯誤しています。
以下のコードでは、開始ボタンを押すと1ミリ秒でafterメソッドを繰り返します。その後、開始ボタンを押せば押すほど回転が早くなり、画面のリフレッシュレートを超えても、残像は生じないものの高速回転しているような錯覚がみられます。
計測すると1ミリ秒以下でafterメソッドが繰り返されています。そして、開始ボタンを押した数と同じだけ、停止ボタンを押すと回転を止めることができます。
質問事項
(1)なぜ高速回転できるのか?
afterメソッドの最小単位は1ミリ秒ときいています。したがって、開始ボタンを押すたびにafterメソッドの1ミリ秒が重なっていくだけなので、その分遅くなりそうなものですが、実際は逆です。感覚的には再帰の深度が深くなっているように推測しますが、どういう機序で起こっているのか分かりません。
(2)マイクロ秒の領域における値のバラツキについて
開始ボタンを押すたびに確かに計測値は短くなっていきます。しかし、計測値をみると倍々で短縮していく訳でもなさそうです。そもそも、1ミリ秒の設定でも時折計測値の外れ値がみられます。これは、処理時間が関与しているためでしょうか。
(3)方法論的に正しいのか
afterメソッドを1ミリ秒以下とする方法論として、コード的に問題はないのでしょうか。また、他に正しい方法はあるのでしょうか。
以上、ご教授お願いします。
該当するソースコード
# -*- coding: utf-8 -*-
import tkinter as tk
import math
import time
import random
# キャンバスのサイズの設定
CANVAS_WIDTH = 400
CANVAS_HEIGHT = CANVAS_WIDTH
CANVAS_SIZE = CANVAS_WIDTH
# 時計の前面と背景の色の設定
BG_COLOR = "black"
#時計の外枠
FG_COLOR = "gray"
# 盤面を表す円の半径の設定
CLOCK_OVAL_RADIUS = CANVAS_SIZE / 2
#針の長さ
R = CLOCK_OVAL_RADIUS*0.95
# キャンバスの中心座標
X0 = CANVAS_HEIGHT/2
Y0 = CANVAS_WIDTH/2
class POV(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.frame0= tk.Frame(self.master, bg="white")
self.frame0.pack()
# 変数定義
self.rad =0
self.after_id = 0
self.time_lst = [0,]
self.create_widget()
self.create_button()
self.draw_hand()
def create_widget(self):
self.canvas = tk.Canvas(self.frame0, width = CANVAS_WIDTH, height = CANVAS_HEIGHT,
bg=BG_COLOR, highlightthickness=0)
#gridは3x3
self.canvas.grid(column=0, row=0, columnspan=3, padx=0, pady=0,
sticky=tk.NSEW,
)
# 時計の盤面を表す円を描画する
x1 = X0 - CLOCK_OVAL_RADIUS
y1 = Y0 - CLOCK_OVAL_RADIUS
x2 = X0+ CLOCK_OVAL_RADIUS
y2 = Y0+ CLOCK_OVAL_RADIUS
self.canvas.create_oval(
x1, y1, x2, y2,
fill="black",
width=2,
outline="gold"
)
def draw_hand(self):
self.hand1 = self.canvas.create_line(
X0, Y0, X0+R*math.cos(self.rad), Y0+R*math.sin(self.rad),
fill="white",
width=10,
)
# math.pi/180が1度なので、3度ずらした針
self.hand2 = self.canvas.create_line(
X0, Y0, X0+R*math.cos(self.rad+ 3*math.pi/180), Y0+R*math.sin(self.rad+3*math.pi/180),
fill="white",
width=10,
)
def rotate_hand(self):
#ループの間隔測定
time_a = time.time()
duration = time_a - self.time_lst[-1]
self.time_lst.append(time_a)
dur_r = round(duration, 5)
#print頻度を調整
r = random.randint(1, 300)
if r ==5:
print(dur_r)
xr = X0+R*math.cos(self.rad)
yr = Y0+R*math.sin(self.rad)
xr_2 = X0+R*math.cos(self.rad+3*math.pi/180)
yr_2 = Y0+R*math.sin(self.rad+3*math.pi/180)
self.canvas.coords(self.hand1, X0, Y0, xr, yr)
self.canvas.coords(self.hand2, X0, Y0, xr_2, yr_2)
#1度ずつふえる
if self.rad > 2*math.pi:
self.rad = self.rad - 2*math.pi
self.rad += math.pi/180
self.after_id = self.after(1, self.rotate_hand)
def create_button(self):
self.start_button = tk.Button(self.frame0, width=20, height=1, text="開始", fg = "gray20",
font=("MSゴシック体", "14"), command=self.start_button_clicked)
self.start_button.grid(column=0, row=1, columnspan=3, sticky=tk.NSEW)
self.stop_button = tk.Button(self.frame0, width=20, height=1, text="停止", fg = "gray20",
font=("MSゴシック体", "14"), command=self.stop_button_clicked)
self.stop_button.grid(column=0, row=2, columnspan=3, sticky=tk.NSEW)
#ボタンを押した時
def start_button_clicked(self):
self.rotate_hand()
def stop_button_clicked(self):
self.after_cancel(self.after_id)
if __name__ == "__main__":
app = tk.Tk()
POV(app)
app.mainloop()