Python 3,concurrent.futures.ProcessPoolExecutor 和 CEF 在达到池大小后崩溃

Python 3, concurrent.futures.ProcessPoolExecutor and CEF crashes after pool size is reached

我在使用 Python 3.9.6 的 Win 10 上,我正在尝试创建一个 concurrent.futures.ProcessPoolExecutor 大小较小的池并向其中添加大量使用 CEF 的任务。

这始终适用于第一个任务,直到达到池大小,之后每个未来都会报告异常

"A process in the process pool was terminated abruptly while the future was running or pending."

只有在我关闭 CEF 时才会出现此问题;当不调用 cef.Shutdown() 时,它不能再被复制。

这是测试代码:

import sys
import concurrent.futures
from cefpython3 import cefpython as cef

def tst():
    settings = { "windowless_rendering_enabled": True }
    try:
        cef.Initialize(settings=settings, switches={})
        cef.Shutdown()
    except:
        print("Unexpected error in tst:", sys.exc_info()[0])

def _main():
    futures = []
    try:
        with concurrent.futures.ProcessPoolExecutor(max_workers=2) as executor:
            for i in range(4):
                futures.append(executor.submit(tst))
        for f in futures:
            print(f'{f._state} - {f.exception()}')
    except:
        print("Unexpected error in main:", sys.exc_info()[0])

if __name__ == '__main__':
    _main()

它也是 available online,但我找不到提供 CEF 的在线 python IDE。

预期输出为

FINISHED - None
FINISHED - None
FINISHED - None

但实际输出是

FINISHED - None
FINISHED - None
FINISHED - A process in the process pool was terminated abruptly while the future was running or pending.

将 max_workers 设置为 4 并将范围设置为 6 将遵循该模式,导致 4 次“None”和 2 次错误。

对于每个失败的任务,windows 应用程序日志将列出类似

的错误
Faulting application name: python.exe, version: 3.9.6150.1013, time stamp: 0x60d9eb23
Faulting module name: libcef.dll, version: 3.3359.1774.0, time stamp: 0x5afd9b5a
Exception code: 0x80000003
Fault offset: 0x0000000001e83c58
Faulting application path: ...\Python39\python.exe
Faulting module path: ...\Python39\lib\site-packages\cefpython3\libcef.dll

我做了一个 second version of the test 试图缩小代码中发生崩溃的位置,它似乎就在 cef.Initialize.

我现在有点迷茫,因为我对 Python 的经验不多,对 CEF 的经验就更少了。是并发包或 CEF 的问题,还是两者不能很好地协同工作?我做错了什么?

CEF 不允许在调用 Shutdown 之后调用 Initialize。有关详细信息,请参阅 here

当您的任务多于工作进程时,这些任务将排队等候稍后在工作进程可用时执行。

前 2 个任务 运行 好的,因为为每个任务创建了一个新进程。

第三个任务失败,因为它运行在一个已经被使用的进程中(即:已初始化)。

一个快速的解决方法是添加一个守卫,例如:

initialized = False

def tst():
    global initialized
    settings = { "windowless_rendering_enabled": True }
    try:
        if not initialized:
            initialized = True
            cef.Initialize(settings=settings, switches={})

            # you cannot call shutdown now, sorry
            #cef.Shutdown()

        # do stuff...
    except:
        print("Unexpected error in tst:", sys.exc_info()[0])

记住:您正在创建新进程,每个进程都有自己的内存space。所以,使用全局变量绝对不会有问题。

更新: 如果你想调用 cef.Shutdown() 你可以这样做(还没有测试过):

import signal

initialized = False

def tst():
    global initialized
    settings = { "windowless_rendering_enabled": True }
    try:
        if not initialized:
            def signal_handler(_, __):
                cef.Shutdown()
            signal.signal(signal.SIGINT, signal_handler)
            signal.signal(signal.SIGTERM, signal_handler)
           
            cef.Initialize(settings=settings, switches={})
            initialized = True

        # do stuff...
    except:
        print("Unexpected error in tst:", sys.exc_info()[0])