2013-02-19 109 views
0

我是Python新手,目前正在开发一个小型应用程序供个人使用。我用我的gui使用tkinter。试图与tkinter(tkinter是在主线程上)运行线程执行任务,而是它停止主线程

我想要做的是创建一个带有标签的Toplevel弹出窗口,根据登录尝试的方式更改文本。所以,当tk运行的主线程显示带有动态文本的弹出窗口时,我想要启动一个线程来尝试登录至多5次,并通过设置名为'logindata'的全局变量向主线程报告。

AuctioneerGUI和LoginThread类中的_login()方法实际上是唯一重要的事情,您可以忽略其余部分,但它们可能认为相关。

当按下登录按钮时调用_login()方法。所有这些都是尝试登录并设置logindata。同时主线程正在循环,直到它注意到LoginThread已经设置了变量,并且它收集了所有三个元素之后,才会通过逻辑的其余部分(这不是完全实现的,但与问题无关)

现在发生的情况是主线程在LoginThread启动后暂停,并且只有在完成时才会继续。即使LoginThread应该在单独的线程中运行,因此不会暂停主线程。所以弹出窗口只在LoginThread任务执行完毕后才显示出来。我希望弹出窗口出现并显示给用户更新的标签。我该怎么做呢?

我确定问题是线程暂停主线程,因为我使用打印确定了这一点。

另外我还有一个小问题。 popup.destroy()似乎没有做任何事情。 TopLevel只停留在那里。

对不起,我的文字墙,并提前感谢帮助我。我已经花费了比我应该尝试几种不同的东西更多的时间,但是我没有设法让它工作。

让我知道如果有什么不清楚,不介意有时效率低下或愚蠢的逻辑,我首先想在使它漂亮之前至少使其功能。

-Daan

global logindata 
logindata = {"counter": -1, "status": -1, "success": -1} 

class AuctioneerGUI: 
    def __init__(self): 
     root = Tk() 
     root.title("Path of Exile Auctioneer") 
     self._setupGUI(root) 
     self._loggingin = False 

     root.protocol("WM_DELETE_WINDOW", lambda: root.quit()) 
     root.mainloop()   

    def _setupGUI(self, root):    
     frame = Frame(root) 

     email = StringVar() 
     pass_ = StringVar() 
     thread = StringVar() 

     email.set("email") 
     pass_.set("password") 
     thread.set("76300") 

     email_label = Label(frame, text="email") 
     self._email_box = Entry(frame, takefocus=True, width=50, textvariable=email) 
     self._email_box.focus_set() 
     pass_label = Label(frame, text="password") 
     self._pass_box = Entry(frame, takefocus=True, show="*", width=50, textvariable=pass_) 
     thread_label = Label(frame, text="thread id") 
     self._thread_box = Entry(frame, takefocus=True, width=10, textvariable=thread) 
     self._login_button = Button(frame, text="login", command=lambda: self._login(root), takefocus=True) 

     frame.pack() 
     email_label.pack() 
     self._email_box.pack() 
     pass_label.pack() 
     self._pass_box.pack() 
     thread_label.pack() 
     self._thread_box.pack() 
     self._login_button.pack() 

    def _login(self, root): 
     self._login_button.configure(command=None) 
     email = self._email_box.get() 
     pass_ = self._pass_box.get() 
     thread = self._thread_box.get() 
     # Check email validity 
     # no whitespaces, 1 @ sign 1 . after the @ sign 
     try: 
      thread = int(thread) 
     except ValueError: 
      return -1 
      #invalid thread 

     if not re.match(r"[^@][email protected][^@]+\.[^@]+", email) or not email.find(" ") == -1: 
      return -1 
      #invalid mail 

     self._sm = SessionManager(email, pass_, thread)  

     self._message = StringVar() 
     self._message.set("Attempt 1/5.") 

     popup = Toplevel(root) 
     popup.title("Logging in...") 
     message_label = Label(popup, text = self._message.get(), textvariable = self._message) 
     message_label.pack() 

     _thread = LoginThread(self._sm)   
     _thread.start() 

     loop = True     

     while loop: 
      counter = -1 
      success = -1 
      status = -1 
      while counter == -1: 
       counter = logindata["counter"] 
       print(counter) 
      while success == -1: 
       success = logindata["success"] 
      print(success) 
      while status == -1: 
       status = logindata["status"] 
      print(status) 
      if success: 
       self._message.set("Attempt {}/5. Success.".format(counter)) 
      elif status == 200: 
       self._message.set("Attempt {}/5. Failed: wrong password.".format(counter)) 
      else: 
       self._message.set("Attempt {}/5. Failed: connection error. {}".format(counter, status)) 
      updatebar = not success 
      logindata["counter"] = -1 
      logindata["status"] = -1 
      logindata["success"] = -1 
      if counter == 5: 
       break 

     popup.destroy() 
     self._login_button["command"] = lambda: self._login(root) 
     self._setup_main_layout(root) 

    def _setup_main_layout(self, root): 
     pass 

class LoginThread(threading.Thread): 

    def __init__(self, sessionmanager): 
     threading.Thread.__init__(self) 
     self._sm = sessionmanager 

    def run(self): 
     success = False 
     counter = 1 
     while not success: 
      if counter > 5: 
       break 

      data = self._sm.login() 
      status = data[1] 
      success = data[0] 
      logindata["counter"] = counter 
      logindata["success"] = success 
      logindata["status"] = status 
      counter += 1 
      print("done") 

更新:

一些研究,我将通过创建从标签是通过管道输送到小部件,通过像在本例中,队列通信继承ThreadSafeLabel解决问题后:

http://effbot.org/zone/tkinter-threads.htm

+0

理解它的工作原理很有用。您仍在投票处理完成情况,这仍然是浪费,但是您只会每100ms执行一次,而不是尽可能快。而且,更重要的是,你让主要的'tkinter'事件循环在间隔100ms内运行,所以GUI不会冻结。这仍然不是理想的,但它是你用纯tkinter做的最好的。 – abarnert 2013-02-19 02:25:15

+0

是的,我意识到尽可能快地通过轮询囤积所有cpu资源并不明智。我发布的代码片段几乎是一个正在进行的混乱。我有一些java线程的经验,但我从来没有像tkinter一样处理它。 – 2013-02-19 02:29:34

+0

我可能会记住错误,但不是Swing基本相同 - 触摸另一个线程中的任何Swing对象会导致异常或神秘崩溃等。除非我非常确定Swing有一个'runLaterOnDispatchThread'方法(和''BackgroundWorker'和'WorkerExecutor'),对吧? – abarnert 2013-02-19 18:48:48

回答

2

第零,因为unutbu指出,你只是运行的其他线程的主线程run功能,所以没有什么事情发生,直到其完成。


一旦你解决这个问题,你永远,永远想有一个线程自旋等待一个变量发生变化,因为你在这里做的:

while counter == -1: 
    counter = logindata["counter"] 
    print(counter) 

主线程不可能做任何事情但是在这里旋转,直到后台线程将logindata["counter"]设置为其他东西。如果强制主线程等待另一个线程完成,那么还可以在主线程中运行其他代码。你的代码与单线程处理有相同的效果,除了它烧尽可能多的CPU尽可能检查值,无论原因。

如果您需要等到某些事情完成,您需要使用某种交叉线程信号,例如threading.Conditionqueue.Queue


然而,这仍然不能解决你的问题,因为主线程会仍然被卡住的_login函数内,直​​到登录完成。这意味着它不能做其他事情,如重画屏幕,处理鼠标点击等。

所以,即使你解决了前两个问题,并且得到了工作,这仍然是完全一样的不产卵线程,只是在主线程中进行登录。

你需要的是一个_login函数,它在启动后台线程后立即返回,然后使用其他一些机制从后台线程触发tkinter循环中的事件。

+0

谢谢。我会研究这个,并做更多的研究,听起来这应该解决我的问题,我现在看到明显的,即那些其他while循环仍然在主线程中。 – 2013-02-19 00:51:42

+0

@DaanLubbers:'tkinter'的最大问题在于它没有线程安全的'run_function_in_main_thread',或者甚至可以使用线程安全的'add_event_to_queue'方法来自己实现一个。你必须做一些搜索来找出如何去做(尽管答案可能在右边的相关问题之一上)。或者搜索mktkinter,它可以让所有主线程外的调用变为“在主线程中运行此函数”调用。 – abarnert 2013-02-19 00:54:06

1

正确的方式开始新的threading.Thread是通过调用start方法,而不是run方法。这是产生新线程的start方法。没有它,你实际上正在主线程中运行。

因此,而不是尝试:

_thread = LoginThread(self._sm)   
    _thread.start() 

the docs

一旦线程对象被创建,它的活动必须通过调用 线程的start()方法来启动。这将调用单独的控制线程中的run()方法。

+0

我不知道那是怎么发生的。我甚至称它是从文本开始的,我很确定这是从某个时刻开始的。 使用开始弹出并不显示,虽然它出现。我按下登录按钮,当http的东西完成时,它会保持按住一秒左右,然后它将取消按下并将正确的数据发送到主线程。所以它似乎仍然以某种方式阻止主线程。或者它可能会在弹出窗口呈现之前以某种方式关闭? – 2013-02-19 00:46:08