【问题标题】:user interface freezed when using concurrent.futures.ThreadPoolExecutor使用 concurrent.futures.ThreadPoolExecutor 时用户界面冻结
【发布时间】:2015-02-08 04:57:06
【问题描述】:

我认为在这里使用 concurrent.futures.ThreadPoolExecutor 时用户界面不应该被冻结,但它不符合我的期望,任何人都可以解释为什么?这里还有其他不让用户界面冻结的解决方案吗?

import sys   
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import time

import concurrent.futures
import urllib.request

URLS = ['http://www.tmall.com/',
        'http://www.cnn.com/',
        'http://huawei.com/',
        'http://www.bbc.co.uk/',
        'http://jd.com/', 
        'http://weibo.com/?c=spr_web_360_hao360_weibo_t001', 
        'http://www.sina.com.cn/', 
        'http://taobao.com',
        'http://www.amazon.cn/?tag=360daohang-23', 
        'http://www.baidu.com/', 
        'http://www.pconline.com.cn/?ad=6347&360hot_site']

# Retrieve a single page and report the url and contents
def load_url(url, timeout):
    conn = urllib.request.urlopen(url, timeout=timeout)
    return conn.readall()




class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        QTimer.singleShot(3000, self.speedCalculate)

#        self.timerC = QTimer();
#        self.timerC.timeout.connect(self.speedCalculate)
#        self.timerC.start(1000)




    def speedCalculate(self):#compare with for loop
        t1=time.clock()
        # We can use a with statement to ensure threads are cleaned up promptly
        with concurrent.futures.ThreadPoolExecutor(max_workers=len(URLS)) as executor:
            # Start the load operations and mark each future with its URL
            future_to_url = {executor.submit(load_url, url, 60): url for url in URLS}
            for future in concurrent.futures.as_completed(future_to_url):
                url = future_to_url[future]
                try:
                    data = future.result()
                except Exception as exc:
                    print('%r generated an exception: %s' % (url, exc))
                else:
                    print('%r page is %d bytes' % (url, len(data)))

        t2=time.clock()
        print('t2-t1-------------', t2-t1)
#        
#        for url in URLS:
#            data=load_url(url, 60)
#            print(url, len(data))
#        
#        t3=time.clock()
#        print('t3-t2-------------', t3-t2)
    #    
if __name__ == '__main__':                         
    app =QApplication(sys.argv)      
    splitter =MainWindow()   
    splitter.show()      
    app.exec_()

【问题讨论】:

    标签: python qt python-3.x pyqt pyside


    【解决方案1】:

    这里有两件事你需要知道:

    1. Slots 在对象的线程中被调用,这意味着在这种情况下speedCalculate 在主线程中被调用。
    2. concurrent.futures.as_completed 在已完成的期货上返回一个迭代器,这意味着在所有期货都完成之前,对此的 for 循环不会完成。

    因此,您的 speedCalculate 方法仅在所有下载完成后才会返回,从而阻塞您的应用程序事件循环。

    您应该做的是以不同的方法完成您当前在 speedCalculate 中所做的所有工作,例如 _speedCalculate 并在 speedCalculate 插槽内的新线程中调用它。

    类似:

    def speedCalculate(self):
        threading.Thread(target=self._speedCalculate).start()
    
    def _speedCalculate(self):#compare with for loop
        t1=time.clock()
        # We can use a with statement to ensure threads are cleaned up promptly
        with concurrent.futures.ThreadPoolExecutor(max_workers=len(URLS)) as executor:
            # Start the load operations and mark each future with its URL
            future_to_url = {executor.submit(load_url, url, 60): url for url in URLS}
            for future in concurrent.futures.as_completed(future_to_url):
                url = future_to_url[future]
                try:
                    data = future.result()
                except Exception as exc:
                    print('%r generated an exception: %s' % (url, exc))
                else:
                    print('%r page is %d bytes' % (url, len(data)))
    
        t2=time.clock()
        print('t2-t1-------------', t2-t1)
    

    【讨论】:

    • 不仅如此,GIL 将阻止 UI 更新,直到计算完成(在这种情况下,它有可能跟上,因为在执行 I/O 时释放 GIL,例如获取 URL通过网络)。
    • 我认为 GIL 在这里根本不是问题,因为没有 cpu 密集型线程正在运行,它会在释放 GIL 后立即通过重新获取 GIL 抢占线程切换,并且所有 python io 方法都会释放吉尔。
    • 1) 你说Slots在对象的线程中被调用,那么slot对应QThread的finished()信号在哪个线程中被调用? 2)是否用户干预暂时冻结会导致事件循环不运行?
    • 1) 除非您使用Qt::DirectConnection 连接(qt-project.org/doc/qt-4.8/qt.html#ConnectionType-enumqt-project.org/doc/qt-4.8/threads-qobject.html 的更多线程信息),否则插槽将在对象的线程中调用(即 QObject.thread(),这通常是创建插槽接收器的线程,除非您调用 QObject.moveToThread())
    • 2) 事件循环被阻塞是用户界面冻结的原因。在这种情况下,主事件循环调用您的插槽 speedCalculate(),它不会很快返回,事件循环无法继续处理其他事件,例如绘制 UI。
    猜你喜欢
    • 2016-07-22
    • 2017-10-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-02
    相关资源
    最近更新 更多