after () вешает окно вывода - PullRequest
       134

after () вешает окно вывода

0 голосов
/ 06 августа 2020

Я пытаюсь создать проект в реальном времени, где статус обновляется каждую секунду, поэтому некоторая часть кода повторяется постоянно. когда я хочу изменить информацию, которая должна быть обновлена, я просто нажимаю новую кнопку, которая дает мне первое окно, в котором я могу обновить новую информацию. но при этом я получаю следующую ошибку. если я использую after () вместо потоковой передачи, ошибки не будет, но окно вывода зависнет. Пожалуйста, помогите мне с идеей решить эту проблему. Спасибо.

Exception in thread Thread-2:
    Traceback (most recent call last):
      File "C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python37_64\lib\threading.py", line 926, in _bootstrap_inner
        self.run()
      File "C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python37_64\lib\threading.py", line 1177, in run
        self.function(*self.args, **self.kwargs)
      File "C:/Users/Desktop/Tool/t.py", line 47, in ae
        self.treeview.insert('', 'end',image=self._img, value=(a))
      File "C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python37_64\lib\tkinter\ttk.py", line 1370, in insert
        res = self.tk.call(self._w, "insert", parent, index, *opts)
    _tkinter.TclError: invalid command name ".!treeview"

Код, с которым у меня проблема:

def aaa(self):
                num_threads = 5 * multiprocessing.cpu_count()
                p = multiprocessing.dummy.Pool(num_threads)
                p.map(self.ping_func, [x for x in Demo2.t1])
                self.process_incoming() 
                #threading.Timer(1.0, self.aaa).start()-this gives the error while pressing new button and updating information
                self.master.after(100, self.aaa) #it hangs the output window 

Пример кода:

import multiprocessing.dummy
import multiprocessing
import os
import socket
import sys
import subprocess
import re
import time
import threading
import tkinter.messagebox
from tkinter import ttk
import queue
from tkinter import *

class Demo1:  #window 1
    data=[]
    def __init__(self, master):
        self.master = master
        self.t=tkinter.Text(self.master,height=20,width=50)
        self.t.grid(row=1, column=1)
        self.button = tkinter.Button(self.master,height=3,width=10, text="OK", command = self.new_window)
        self.button.grid(row=2,column=1)

    def new_window(self):
        self.inputValue=self.t.get("1.0",'end-1c')
        Demo1.data=self.inputValue.split("\n")
        self.master.destroy() # close the current window
        self.master = tkinter.Tk() # create another Tk instance
        self.app = Demo2(self.master) # create Demo2 window
        self.master.mainloop()

class Demo2: #window 2
    value = []
    display = []
    num=0
    def __init__(self, master):
        self.master = master
        self.queue = queue.Queue()
        Demo2.value = Demo1.data
        self.button = tkinter.Button(self.master,height=2,width=11, text="new",command=self.new).place(x=0,y=0)
        self.label = tkinter.Label(self.master, text="monitor", font=("Arial",20)).grid(row=0, columnspan=3)
        cols = ('aa','bb')
        self.treeview = ttk.Treeview(self.master, columns=cols)
        for col in cols:
            self.treeview.heading(col, text=col)
            self.treeview.column(col,minwidth=0,width=170)
        self.treeview.grid(row=1, column=0)
        self._img=tkinter.PhotoImage(file="green1.gif")
        self.aaa()
        
    def aaa(self):
                num_threads = 5 * multiprocessing.cpu_count()
                p = multiprocessing.dummy.Pool(num_threads)
                p.map(self.ping_func, [x for x in Demo2.value])
                self.process_incoming() 
                #threading.Timer(1.0, self.aaa).start()
                self.master.after(100, self.aaa)
                
    def ping_func(self,ip):   #Ping every ip and append the result 
            ping_result = []
            pingCmd = "ping -n 1 -w 1000 " + ip
            childStdout = os.popen(pingCmd)
            result = (childStdout.readlines())
            childStdout.close()
            ping_result.append(ip)
            if(any('Reply from' in i for i in result)):
                ping_result.append("success")
            else:
                ping_result.append("failed")
            self.queue.put(ping_result)  #Thread value to queue
            
    def process_incoming(self):   #add the ping result to treeview
        while self.queue.qsize():
            try:
                if Demo2.num<len(Demo1.data):
                    self._img=tkinter.PhotoImage(file="green1.gif")
                    self._img1=tkinter.PhotoImage(file="red.gif")
                    msg = self.queue.get_nowait()
                    Demo2.display.append(msg)  #adding queue value to variable(display)
                    if(len(Demo2.display)==len(Demo1.data)):      
                        self.treeview.insert("","end",values=(0,0,0,0,0))
                        self.treeview.delete(*self.treeview.get_children())
                        for i,(a,b) in enumerate(Demo2.display):
                            if(Demo2.display[i][1]=='success' ):
                                self.treeview.insert('', 'end',image=self._img, value=(a,b))
                            else:
                                self.treeview.insert('', 'end',image=self._img1, value=(a,b))
                        Demo2.num=Demo2.num+1
                        Demo2.display.clear()
                else:
                    Demo2.display.clear()
                    Demo2.num=0     
            except queue.Empty:  # Shouldn't happen.
                pass
            
    def periodic_call(self):
        self.master.after(200, self.periodic_call) # checking its contents periodically
        self.process_incoming()
        if not self.running:
            import sys
            sys.exit(1)       

    def new(self):
        self.master.destroy() # close the current window
        self.master = tkinter.Tk() # create another Tk instance
        self.app = Demo1(self.master) # create Demo2 window
        self.master.mainloop()

def main():
    root = tkinter.Tk()
    app = Demo1(root)
    root.mainloop()


if __name__ == '__main__':
    main()

1 Ответ

1 голос
/ 06 августа 2020

Основная проблема заключается в том, что ping -w 1000 требует много времени для запуска, но Pool.map() ждет всех результатов. Вы даже можете запустить results = p.map(...) без queue (но с return result), но он также может заблокировать tkinter

Вы можете использовать map_async(), чтобы запустить его, не дожидаясь результатов.

Я также использую starmap (или скорее starmap_async()) для отправки двух аргументов ip, queue каждому процессу.

Я также внес другие изменения - ie. переименуйте переменные, переместите код в __init__, чтобы создать только один раз (изображения, Pool, Queue). Я также отправляю список IP в другое окно в качестве аргумента Window2(master, data) и обратно Window1(master, data) - поэтому я могу редактировать этот список.

BTW:, потому что я запускаю Linux, поэтому я изменил аргументы для ping и проверьте другой текст, чтобы проверить, получил ли он ответ.

import os
import multiprocessing.dummy
import queue
import tkinter as tk
import tkinter.ttk as ttk

# --- classes ---

class Window1:
    
    def __init__(self, master, data=None):
        self.master = master
        
        self.data = data
        
        if self.data is None:
            self.data = []

        self.text = tk.Text(self.master, height=20, width=50)
        self.text.grid(row=1, column=1)
        
        self.button = tk.Button(self.master, height=3, width=10, text="OK", command=self.new_window)
        self.button.grid(row=2, column=1)

        # put self.data in Text
        #for item in self.data:
        #    self.text.insert('end', item + '\n')
        self.text.insert('end', '\n'.join(self.data))
            
    def new_window(self):
        text = self.text.get('1.0', 'end')
        
        # remove empty lines
        self.data = [item.strip() for item in text.split("\n") if item.strip()]
        
        self.master.destroy()
        
        root = tk.Tk()
        Window2(root, self.data)
        root.mainloop()
        
        
class Window2:
    
    def __init__(self, master, data):
        self.master = master

        # keep list
        self.data = data
        
        # create dictionary for results
        self.results = {ip: [ip, '???'] for ip in self.data}
        
        self.button = tk.Button(self.master, height=2, width=11, text='New', command=self.new)
        self.button.grid(row=0, column=0, sticky='w')

        self.label = tk.Label(self.master, text='monitor', font=("Arial", 20))
        self.label.grid(row=0, column=1)

        cols = ('IP','Result')
        
        self.treeview = ttk.Treeview(self.master, columns=cols)
        for col in cols:
            self.treeview.heading(col, text=col)
            self.treeview.column(col,minwidth=0,width=170)
        self.treeview.grid(row=1, column=0, columnspan=3)
        
        # create only once
        self._image_green = None # tk.PhotoImage(file="green1.gif")
        self._image_red   = None # tk.PhotoImage(file="red.gif")

        # to reduce number of processes for small `data`
        n = min(5, len(self.data))

        # create only once
        self.queue = queue.Queue()
        self.num_threads = n * multiprocessing.cpu_count()
        self.p = multiprocessing.dummy.Pool(self.num_threads)

        # to stop `after()`
        self.running = True        

        # run first time
        self.update_treeview()  # to display it before running processes

        self.run_processes()
        self.processes_incoming() # first create window to display it faster
        
    def run_processes(self):
        if self.running:
            self.p.starmap_async(self.ping, [(ip, self.queue) for ip in self.data])
            self.after_ID2 = self.master.after(500, self.run_processes)
        
    def ping(self, ip, queue):
        #print('start ping:', ip)
        
        #cmd = 'ping -n 1 -w 3 ' + ip
        cmd = 'ping -w 1 ' + ip  # Linux
        
        child_stdout = os.popen(cmd)
        result = child_stdout.readlines()
        child_stdout.close()
        
        #print('end ping:', ip)

        #if any('Reply from' in line for line in result):
        if any('bytes from' in line for line in result): # Linux
            value = [ip, 'success']
        else:
            value = [ip, 'failed']
            
        queue.put(value)
        
    def update_treeview(self):
        self.treeview.delete(*self.treeview.get_children())
        
        for ip in self.data:
            ip, value = self.results[ip]
            if value == 'success':
                image = self._image_green
            elif value == 'failed':
                image = self._image_red
            else:
                image = None

            #self.treeview.insert('', 'end', image=image, value=(ip, valueb))
            self.treeview.insert('', 'end', value=(ip, value))

    def processes_incoming(self):
       
       if self.running:

            # get all from queue
            new_values = False
            while self.queue.qsize():
            #while not self.queue.empty:
                data = self.queue.get_nowait()
                ip, value = data
                self.results[ip] = data
                new_values = True

            # update only if new values    
            if new_values:
                self.update_treeview()
                
            # repeate after 100ms    
            self.after_ID1 = self.master.after(100, self.processes_incoming) 

    def new(self):
        # to stop all `after()`
        self.running = False
        self.master.after_cancel(self.after_ID1)
        self.master.after_cancel(self.after_ID2)
        
        self.master.destroy()
        
        root = tk.Tk()
        Window1(root, self.data)
        root.mainloop()

# --- functions ---

def main():
    examples = [
        '127.0.0.1',    # localhost
        '10.0.0.1',     # IP in local network
        '192.168.0.1',  # IP in local network
        '8.8.8.8',      # Google DNS
        '8.8.4.4',      # Google DNS
    ]
    
    root = tk.Tk()
    Window1(root, examples)
    root.mainloop()

# --- main ---

if __name__ == '__main__':
    main()
...