2011-12-21 7 views
5

Tôi có một quá trình sẽ mất một lúc (có thể là một hoặc hai phút) để hoàn tất. Khi tôi gọi nó từ GUI pygtk của tôi, cửa sổ sẽ khóa (tối và ngăn chặn hành động của người dùng) sau khoảng 10 giây.Ngừng pygtk GUI khỏi khóa trong quá trình chạy dài

Tôi muốn dừng điều này xảy ra, nhưng tôi không chắc chắn như thế nào. Tôi nghĩ rằng đa luồng sẽ là câu trả lời, nhưng nó dường như không hoạt động. Tôi đã thử hai phương pháp khác nhau mà tôi tìm thấy trực tuyến. Đầu tiên, tôi sửa đổi this FAQ để có một chức năng chạy dài. Thứ hai tôi đã thử sử dụng luồng.Thread trực tiếp như trong this answer, nhưng điều đó cũng bị khóa.

Hai mẫu của tôi dưới đây. Tôi mới sử dụng đa luồng, vì vậy có lẽ đó không phải là giải pháp mà tôi đang tìm kiếm. Về cơ bản, tôi chỉ cố gắng giữ giao diện GUI khóa lại để tôi có thể cập nhật với thanh tiến trình và cho phép người dùng sử dụng nút hủy.

#Sample 1 
import threading 
import time 
import gobject 
import gtk 

gobject.threads_init() 

class MyThread(threading.Thread): 
    def __init__(self, label, button): 
     super(MyThread, self).__init__() 
     self.label = label 
     self.button = button 
     self.counter = 0 
     button.connect("clicked", self.on_button_click) 
     self.quit = False 

    def update_label(self, counter): 
     self.label.set_text("Counter: %i" % counter) 
     time.sleep(20) 
     return False 

    def on_button_click(self, widget): 
     self.counter += 1 
     gobject.idle_add(self.update_label, self.counter) 

window = gtk.Window() 
label = gtk.Label() 
box = gtk.VBox() 
button = gtk.Button("Test") 
box.pack_start(label) 
box.pack_start(button) 
window.add(box) 
window.show_all() 
window.connect("destroy", lambda _: gtk.main_quit()) 
thread = MyThread(label, button) 
thread.start() 

gtk.main() 
thread.quit = True 

##################################### 
#Sample 2 

from threading import Thread 
import time 
import gobject 
import gtk 

class Test(): 
    def __init__(self): 
     self.counter = 0 
     self.label = gtk.Label() 
     button = gtk.Button("Test") 

     window = gtk.Window() 
     box = gtk.VBox() 
     box.pack_start(self.label) 
     box.pack_start(button) 
     window.add(box) 

     window.connect("destroy", lambda _: gtk.main_quit()) 
     button.connect("clicked", self.on_button_click) 
     window.show_all() 

    def update_label(self, counter): 
     self.label.set_text("Counter: %i" % counter) 
     time.sleep(20) 
     return False 

    def on_button_click(self, widget): 
     self.counter += 1 
     thread = Thread(target=self.update_label, args=(self.counter,)) 
     thread.start() 
     while thread.is_alive(): 
      pass 
     thread.stop() 

test = Test() 
gtk.main() 

Trả lời

7

Hãy tìm thấy bên dưới một phiên bản sửa đổi của ví dụ thứ hai mà làm việc cho tôi:

import threading 
import time 
import gtk, gobject, glib 

gobject.threads_init() 

class Test(): 
    def __init__(self): 
     self.counter = 0 
     self.label = gtk.Label() 
     self.progress_bar = gtk.ProgressBar() 
     self.progress_bar_lock = threading.Lock() 
     button = gtk.Button("Test") 

     window = gtk.Window() 

     box = gtk.VBox() 
     box.pack_start(self.label) 
     box.pack_start(self.progress_bar) 
     box.pack_start(button) 
     window.add(box) 

     window.connect("destroy", lambda _: gtk.main_quit()) 
     button.connect("clicked", self.on_button_click) 
     window.show_all() 

    def update_label(self, counter): 
     self.label.set_text("Thread started (counter: {0})" 
          .format(counter)) 
     time.sleep(5) 
     self.label.set_text("Thread finished (counter: {0})" 
          .format(counter)) 
     return False 

    def pulse_progress_bar(self): 
     print threading.active_count() 
     if threading.active_count() > 1: 
      self.progress_bar.pulse() 
      return True 

     self.progress_bar.set_fraction(0.0) 
     self.progress_bar_lock.release() 
     return False 

    def on_button_click(self, widget): 
     self.counter += 1 
     thread = threading.Thread(target=self.update_label, 
            args=(self.counter,)) 
     thread.start() 

     if self.progress_bar_lock.acquire(False): 
      glib.timeout_add(250, self.pulse_progress_bar) 


if __name__ == '__main__': 
    test = Test() 
    gtk.main() 

Các thay đổi được thực là:

  • Tránh chờ đợi trong callback cho thread để kết thúc để giữ các sự kiện xử lý vòng lặp chính.
  • Đã thêm thanh tiến trình để hiển thị khi chuỗi đang được thực hiện.
  • Đã sử dụng glib.timeout_add để lên lịch cuộc gọi lại làm xung thanh tiến trình khi một số chuỗi đang được thực thi. Điều này có tác dụng tương tự như việc bỏ phiếu cho luồng, nhưng với lợi thế là vòng lặp chính vẫn đáp ứng với các sự kiện khác.
  • Được sử dụng threading.Lock để cho phép gọi lại được lên lịch nhiều lần, bất kể số lần nút được nhấp.
  • Đã thêm gobject.threads_init bị thiếu trong ví dụ này (không có trong ví dụ trước).

Bây giờ, khi nhấp vào nút, bạn sẽ thấy nhãn được nhấp như thế nào và thanh tiến trình xung đến chừng nào chuỗi đang chạy.

+0

Được rồi, điều này có ý nghĩa. Tôi hiểu tại sao tôi không thể liên tục thăm dò ý kiến ​​về trạng thái của luồng. Nhưng tôi cần biết khi nào sợi hoàn tất. Nếu tôi thêm thanh trạng thái cho tiến trình của luồng, làm cách nào tôi có thể biết thời điểm dừng thanh trạng thái? –

+0

Tôi đã thêm thanh tiến trình vào ví dụ. 'glib.timeout_add' cho phép bạn thăm dò trạng thái luồng mà không làm cho ứng dụng của bạn không phản hồi. – jcollado

+0

Tuyệt vời. Điều này thật ý nghĩa. Tôi không biết về 'glib.timeout_add'. Cảm ơn bạn đã giúp đỡ. –

0

Bạn nên triển khai lại Thread.run cho từng chủ đề của mình và bắt đầu vòng lặp sự kiện trong đó.

Ngoài ra, bạn có thể thực hiện nút nhấn gọi phương thức start cho một chuỗi, sau đó gọi số run và thực hiện tác vụ dài của bạn. Bằng cách này, bạn không cần một vòng lặp sự kiện trong mỗi luồng.

Dưới đây là một số mã đơn giản để giải thích những gì tôi muốn nói cho các tùy chọn thứ hai:

class MyThread(threading.Thread): 

    def __init__(self, label, button): 
     threading.Thread.__init__(self) 
     self.label = label 
     self.button = button 
     self.counter = 0 

    def run(self): 
     time.sleep(20) 

def callback(): 
    label.set_text("Counter: %i" % thread.counter) 
    thread.start() 

window = gtk.Window() 
label = gtk.Label() 
box = gtk.VBox() 
button = gtk.Button('Test') 
box.pack_start(label) 
box.pack_start(button) 
window.add(box) 
window.show_all() 

thread = MyThread(label, button) 
button.connect('clicked', callback) 

tôi sử dụng một chức năng gọi lại vì tôi nghi ngờ rằng set_text là thread-safe.

+0

Rất tiếc, tôi đã dán cùng một ví dụ hai lần. Trong ví dụ thứ hai, tôi có nút gọi là thread.start(), sau đó đợi chuỗi kết thúc. Điều này vẫn còn khóa ứng dụng của tôi. –

+0

Trong ví dụ thứ hai bạn đang bỏ phiếu với 'thread.is_alive' bên trong một phương thức gọi lại. Điều đó khiến cho việc gọi lại mất nhiều thời gian để hoàn thành như chính chuỗi đó và ứng dụng của bạn không xử lý bất kỳ sự kiện nào khác cho đến khi cuộc gọi lại kết thúc. – jcollado

+0

@ d-k Tôi quan tâm đến tùy chọn chạy vòng lặp sự kiện trong mỗi chuỗi vì tôi chưa bao giờ thấy điều đó trong mã thực.Bạn có thể giải thích về điều đó không? – jcollado