繁体   English   中英

我应该如何让Tkinter IRC客户端从IRC服务器连续读取数据?

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM