![](/img/trans.png)
[英]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.