簡體   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