I'm writing a Python program which periodically scrapes a website for a single number, which is then displayed in the system tray via Pystray. I can make it do what I want using threading
but would it be better to use asyncio
- and if so, how?
From the Pystray manual:
The call to
pystray.Icon.run()
is blocking, and it must be performed from the main thread of the application. The reason for this is that the system tray icon implementation for OSX will fail unless called from the main thread, and it also requires the application runloop to be running.pystray.Icon.run()
will start the runloop.
Does this mean that asyncio
is incompatible and for this application there must be a separate thread?
Here's a working demo using threading
:
from pystray import Icon as icon, Menu as menu, MenuItem as item
from PIL import Image, ImageDraw, ImageFont
import threading
import time
import random
def make_number_icon(number):
# draws the number as two digits in red on a transparent background
img = Image.new('RGBA', (64,64), color=(0,0,0,0))
fnt = ImageFont.truetype(r'C:\Windows\Fonts\GOTHICB.TTF', 58)
d = ImageDraw.Draw(img)
d.text((0,-8), f"{number:02}", font=fnt, fill='red')
return img
def update_icon(icon, _=None):
# web-scraping replaced with an RNG for demo
count = random.randrange(100)
icon.icon = make_number_icon(count)
icon = icon("Pystray test",
icon=make_number_icon(0),
menu=menu(item("Update now", update_icon),
item("Quit", icon.stop)))
def update_periodically_forever():
global icon
while True:
update_icon(icon)
time.sleep(2) # shortened from 60 for demo
# I understand daemon=True cleans up this thread when we reach EOF
update_thread = threading.Thread(target=update_periodically_forever, daemon=True)
update_thread.start()
# this is a blocking call; we stay here until icon.stop() is called
icon.run()
What it looks like:
update_icon
should be called roughly every 60 seconds. It doesn't matter if that's 60 seconds after the function was last called or 60 second after the function returned from its previous call.