[英]Python IRC bot that reads data returned from a command of the IRC server
[英]How should I get my Tkinter IRC client to continuously read data from the IRC server?
我正在用Python編寫一個IRC客戶端小程序。 我有一個名為Main的Tkinter.Tk子類來管理整個應用程序,該子類在其__init__
方法中創建了一個套接字。 我已經在交互模式下玩過套接字,所以我知道如何使用類似這樣的方法與IRC服務器通信:
>>> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> s.connect(("irc.foonetic.net", 6667))
>>> s.recv(1000)
":anchor.foonetic.net NOTICE AUTH :*** Looking up your hostname...\r\n:anchor.foonetic.net NOTICE AUTH :*** Couldn't resolve your hostname; using your IP address instead\r\n"
>>> s.send("PASS mypassword\r\n")
也就是說,我使用.send
和.recv
進行整個對話。 因此,為了在Tkinter應用程序中獲得用戶輸入,我想我將有一個事件處理程序映射到Enter鍵,它將調用.send
。 但是,我應該在哪里撥打.recv
呢? 我唯一知道的方法是使用計時器每隔幾秒鍾調用一次.recv
,但是由於多種原因,這顯然不是一個好的解決方案。 如果沒有數據要接收,我如何處理.recv
阻塞幾秒鍾(由您設置的超時決定)的事實? 我知道我可以使用Google“多線程”,但是我想了解有關針對這種特定情況的最佳方法的一些指導。
在我的項目中,我為長期I / O(例如套接字讀/寫)設置了一個新線程。 要編寫實用的GUI程序,您遲早必須面對多線程。 這是因為GUI框架具有事件隊列和事件循環。 事件循環通常是一個while循環,在該循環中,它從事件隊列中獲取事件並將該事件分派給已注冊的函數。 如下所示:
while event is not QUIT:
event = event_queue.get(block=True)
dispatch(event)
在dispatch
,直接調用在該事件上注冊的所有回調函數。
此類代碼可在GUI線程中運行,並且如果您在GUI回調中執行長期I / O或阻止操作,則該線程將在該回調中被阻止。 就事件循環而言,該程序在調度函數中被阻塞,該函數稱為阻塞回調函數。 事件隊列中的任何新事件將不被處理。 結果,由於GUI的更新事件被阻止,程序看起來像死了。
當您設置了工作線程來處理耗時的事情時,請勿嘗試直接從該工作線程操作GUI窗口小部件。 大多數GUI框架不是線程安全的,它們通過事件隊列保持操作順序。 在非GUI線程中操作小部件將破壞此順序。
我們可以將事件從非GUI線程添加到事件隊列,然后讓GUI線程處理該事件,以保持順序。 對於某些通用語言,這是正常的方式,但對於python,則不是。 在python中,函數和方法是第一類對象,因此我們可以將其放在隊列中。 不幸的是,tkinter的事件隊列不支持此功能。
Mark Lutz撰寫的《 Python編程》中有很多關於tkinter編程的內容。 在本書中,作者介紹了一種在tkinter中執行多線程的好方法。 這是我的演示:
# python3 source code
from tkinter import *
from tkinter.ttk import *
import threading
import time
import queue
root = Tk()
msg = StringVar()
Label(root, textvariable=msg).pack()
# This is our own event queue, each element should be in this form:
# (function_to_be_called_from_gui_thread, function_arguments)
# In python, functions are objects and can be put in a queue.
my_event_queue = queue.Queue()
def worker():
"""
This is a time consuming worker, it takes 1 second for each task.
If you put such a worker in the GUI thread, the GUI will be blocked.
"""
task_counter = 0
while True:
time.sleep(1) # simulate a time consuming task
# show how many tasks finished in the Label. We put this action in my_event_queue instead of handle
# it from this worker thread which is not safe. This action will be handled by my_event_handler which is
# called from GUI thread.
my_event_queue.put((msg.set, '{} tasks finished.'.format(task_counter)))
task_counter += 1
def my_event_handler():
"""
Query my_event_queue, and handle one event per time.
"""
try:
func, *args = my_event_queue.get(block=False)
except queue.Empty:
pass
else:
func(*args)
# At last schedule handling for next time.
# Every 100 ms, my_event_handler will be called
root.after(100, my_event_handler)
threading.Thread(target=worker, daemon=True).start() # start worker in new thread
my_event_handler() # start handler, after root.mainloop(), this method will be called every 100ms. Or you can use root.after(100, my_event_handler)
root.mainloop()
這是運行畫面。 您可以看到我在運行時調整了窗口的大小。(好吧,我沒有足夠的聲譽來發布圖像,因此您必須自己嘗試一下)
最后,我建議您看一下tkinter編程的Programming Python 。 所有Python代碼都在Python3中。
總的來說,我對Python很陌生,對Tk / ttk也很陌生。 但是,這是我一直在Tk / ttk中用於事件觸發/信號和輔助線程工作的示例。 我知道有些人會討厭單例裝飾器,並且我知道還有其他方法可以從其他類中調用代碼,但是觸發器類非常方便,而工作者類的工作原理很吸引人。 他們在一起使事情變得超級容易。
鳴謝:worker類是在Pithos中找到的GObject worker的非常細微修改的版本,而singleton裝飾器是我在stackoverflow上的某個地方發現的東西的細微修改版本。
import sys
import tkinter
from tkinter import ttk
from tkinter import StringVar
import threading
import queue
import traceback
import time
class TkWorkerThreadDemo:
def __init__(self):
self.root = tkinter.Tk()
self.trigger = Trigger.Singleton()
self.trigger.connect_event('enter_main_thread', self.enter_main_thread)
self.worker = Worker()
self.root.title('Worker Thread Demo')
self.root.resizable(width='False', height='False')
self.test_label_text = StringVar()
self.test_label_text.set('')
self.slider_label_text = StringVar()
self.slider_label_text.set('Press either button and try to move the slider around...')
mainframe = ttk.Frame(self.root)
test_label = ttk.Label(mainframe, anchor='center', justify='center', textvariable=self.test_label_text)
test_label.pack(padx=8, pady=8, fill='x')
slider_label = ttk.Label(mainframe, anchor='center', justify='center', textvariable=self.slider_label_text)
slider_label.pack(padx=8, pady=8, expand=True, fill='x')
self.vol_slider = ttk.Scale(mainframe, from_=0, to=100, orient='horizontal', value='100', command=self.change_slider_text)
self.vol_slider.pack(padx=8, pady=8, expand=True, fill='x')
test_button = ttk.Button(mainframe, text='Start Test with a Worker Thread', command=self.with_worker_thread)
test_button.pack(padx=8, pady=8)
test_button = ttk.Button(mainframe, text='Start Test in the Main Thread', command=self.without_worker_thread)
test_button.pack(padx=8, pady=8)
mainframe.pack(padx=8, pady=8, expand=True, fill='both')
self.root.geometry('{}x{}'.format(512, 256))
def enter_main_thread(self, callback, result):
self.root.after_idle(callback, result)
def in_a_worker_thread(self):
msg = 'Hello from the worker thread!!!'
time.sleep(10)
return msg
def in_a_worker_thread_2(self, msg):
self.test_label_text.set(msg)
def with_worker_thread(self):
self.test_label_text.set('Waiting on a message from the worker thread...')
self.worker.send(self.in_a_worker_thread, (), self.in_a_worker_thread_2)
def in_the_main_thread(self):
msg = 'Hello from the main thread!!!'
time.sleep(10)
self.in_the_main_thread_2(msg)
def in_the_main_thread_2(self, msg):
self.test_label_text.set(msg)
def without_worker_thread(self):
self.test_label_text.set('Waiting on a message from the main thread...')
self.root.update_idletasks()#without this the text wil not get set?
self.in_the_main_thread()
def change_slider_text(self, slider_value):
self.slider_label_text.set('Slider value: %s' %round(float(slider_value)))
class Worker:
def __init__(self):
self.trigger = Trigger.Singleton()
self.thread = threading.Thread(target=self._run)
self.thread.daemon = True
self.queue = queue.Queue()
self.thread.start()
def _run(self):
while True:
command, args, callback, errorback = self.queue.get()
try:
result = command(*args)
if callback:
self.trigger.event('enter_main_thread', callback, result)
except Exception as e:
e.traceback = traceback.format_exc()
if errorback:
self.trigger.event('enter_main_thread', errorback, e)
def send(self, command, args=(), callback=None, errorback=None):
if errorback is None: errorback = self._default_errorback
self.queue.put((command, args, callback, errorback))
def _default_errorback(self, error):
print("Unhandled exception in worker thread:\n{}".format(error.traceback))
class singleton:
def __init__(self, decorated):
self._decorated = decorated
self._instance = None
def Singleton(self):
if self._instance:
return self._instance
else:
self._instance = self._decorated()
return self._instance
def __call__(self):
raise TypeError('Singletons must be accessed through `Singleton()`.')
@singleton
class Trigger:
def __init__(self):
self._events = {}
def connect_event(self, event_name, func, *args, **kwargs):
self._events[event_name] = func
def disconnect_event(self, event_name, *args, **kwargs):
if event_name in self._events:
del self._events[event_name]
def event(self, event_name, *args, **kwargs):
if event_name in self._events:
return self._events[event_name](*args, **kwargs)
def main():
demo = TkWorkerThreadDemo()
demo.root.mainloop()
sys.exit(0)
if __name__ == '__main__':
main()
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.