Python 3 - KeyboardInterrupt в фоновом потоке не обнаруживается основным потоком, пока пользователь не парит над окном GUI - PullRequest
0 голосов
/ 19 ноября 2018

Я написал приложение на основе Python 3 TkInter, которое запускает рабочий поток в фоновом режиме.После завершения рабочего потока он ждет две секунды (чтобы избежать возможного состояния гонки), а затем отправляет KeyboardInterrupt, чтобы сообщить основному потоку, что он может закрыться.

Ожидаемое поведение: при запуске программы запускается окно графического интерфейса пользователя, выводится текст на консоль, после чего программа автоматически закрывается.

Фактическое поведение: вместо автоматического закрытия, это происходит только послеПользователь либо наводит указатель мыши на область окна GUI, либо нажимает клавишу на клавиатуре!Кроме того, программа работает без сообщений об ошибках.

Кто-нибудь имеет представление, почему это происходит, и как это исправить?Я уже пытался обернуть KeyboardInterrupt в отдельную функцию, а затем вызвать ее через объект таймера , но это приводит к тому же поведению.

Я смог воспроизвести эту проблему на2 разные машины Linux, на которых работает Python 3.5.2.и 3.6.6 соответственно.

#! /usr/bin/env python3

import os
import threading
import _thread as thread
import time
import tkinter as tk
import tkinter.scrolledtext as ScrolledText

class myGUI(tk.Frame):

    # This class defines the graphical user interface 

    def __init__(self, parent, *args, **kwargs):
        tk.Frame.__init__(self, parent, *args, **kwargs)
        self.root = parent
        self.build_gui()

    def build_gui(self):                    
        # Build GUI
        self.root.title('TEST')
        self.root.option_add('*tearOff', 'FALSE')
        self.grid(column=0, row=0, sticky='ew')
        self.grid_columnconfigure(0, weight=1, uniform='a')

        # Add text widget to display logging info
        st = ScrolledText.ScrolledText(self, state='disabled')
        st.configure(font='TkFixedFont')
        st.grid(column=0, row=1, sticky='w', columnspan=4)

def worker():
    """Skeleton worker function, runs in separate thread (see below)"""  

    # Print some text to console
    print("Working!")
    # Wait 2 seconds to avoid race condition
    time.sleep(2)
    # This triggers a KeyboardInterrupt in the main thread
    thread.interrupt_main()

def main():
    try:
        root = tk.Tk()
        myGUI(root)
        t1 = threading.Thread(target=worker, args=[])
        t1.start()
        root.mainloop()
        t1.join()
    except KeyboardInterrupt:
        # Close program if subthread issues KeyboardInterrupt
        os._exit(0)

main()

(ссылка на Github Gist на приведенный выше скрипт здесь )

1 Ответ

0 голосов
/ 19 ноября 2018

root.mainloop() mainloop блокирует, и ожидающие (перехватываемые) сигналы в Python проверяются только между выполнением инструкций байт-кода. t1.join() в вашем коде фактически никогда не выполняется.

Поскольку mainloop блок ожидает перенаправленных аппаратных прерываний, для разблокировки вы должны предоставить их, например, зависший над окном, как ты видел. Только , затем интерпретатор обнаруживает ожидающий KeyboardInterrupt. Так работает обработка сигналов в Python.

Решение общей проблемы может означать поиск способов разблокировать блокирующие вызовы ввода-вывода, внеся извне то, что необходимо для их разблокировки, или просто не используя блокирующие вызовы.

Для вашей конкретной установки вы можете убить весь процесс необработанным SIGTERM, но, конечно, это было бы очень, очень уродливо, и здесь также не нужно. Если вы просто ищете способ для тайм-аута своего окна, вы можете использовать тайм-аут с помощью метода tkinter.Tk.after (показано здесь и здесь ), или вы избавляетесь от mainloop и запускаете свой цикл самостоятельно ( здесь ).

Последний может выглядеть так:

def main():
    root = tk.Tk()
    myGUI(root)

    t1 = threading.Thread(target=worker, args=[])
    t1.start()

    while True:
        try:
            root.update_idletasks()
            root.update()
            time.sleep(0.1)
        except KeyboardInterrupt:
            print('got interrupt')
            break

    t1.join()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...