首页 > 编程问答 >试图理解这个错误:致命的Python错误:PyEval_RestoreThread:该函数必须在持有GIL的情况下调用

试图理解这个错误:致命的Python错误:PyEval_RestoreThread:该函数必须在持有GIL的情况下调用

时间:2024-07-23 04:50:14浏览次数:19  
标签:python tkinter winapi

我有一个小型 tkinter 应用程序,我一直在其中实现最小的“拖放”,主要作为学习实验。我真正关心的是删除文件的文件路径。一切实际上都工作正常,直到我尝试在拖放后打包标签小部件。下面的最小工作示例。有问题的行会用注释指出。

我通常不会在调试方面遇到太多麻烦,但我只是不知道从这里该去哪里。我知道 GIL 是什么,但不知道为什么或如何成为一个问题。

完整错误:

Fatal Python error: PyEval_RestoreThread: the function must be called with the GIL held, after Python initialization and before Python finalization, but the GIL is released (the 
current Python thread state is NULL)
Python runtime state: initialized

Current thread 0x000030e0 (most recent call first):
  File "C:\<User>\Python\Lib\tkinter\__init__.py", line 1504 in mainloop
  File "c:\Users\<User>\Desktop\<directory>\ui.py", line 80 in __init__
  File "c:\Users\<User>\Desktop\<directory>\ui.py", line 83 in <module>

Extension modules: _win32sysloader, win32api, win32comext.shell.shell, win32trace (total: 4)

ui.py

import tkinter as tk
from tkinter import ttk

import pythoncom

from dnd import DropTarget


class ScrollFrame(ttk.Labelframe):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.canvas = tk.Canvas(self, highlightthickness=0)
        self.frame = ttk.Frame(self.canvas, padding=(10, 0))
        self.scrollbar = ttk.Scrollbar(
            self, orient='vertical', command=self.canvas.yview
        )
        self.canvas.configure(yscrollcommand=self.scrollbar.set)
        self.canvas.create_window((0, 0), window=self.frame, anchor='n')

        self.canvas.pack(side='left', anchor='n', fill='both', expand=True)
        self.scrollbar.pack(side='right', fill='y')

        self.frame.bind('<Configure>', self.on_resize)
        self.frame.bind(
            '<Enter>',
            lambda _: self.canvas.bind_all('<MouseWheel>', self.on_scroll)
        )
        self.frame.bind(
            '<Leave>',
            lambda _: self.canvas.unbind_all('<MouseWheel>')
        )

    def on_resize(self, _):
        self.canvas.configure(scrollregion=self.canvas.bbox('all'))

    def on_scroll(self, e):
        if self.canvas.winfo_height() < self.frame.winfo_height():
            self.canvas.yview_scroll(-e.delta // 120, 'units')


class TrackList(ScrollFrame):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.track_list = set()

        hwnd = self.winfo_id()
        pythoncom.OleInitialize()
        pythoncom.RegisterDragDrop(
            hwnd,
            pythoncom.WrapObject(
                DropTarget(self),
                pythoncom.IID_IDropTarget,
                pythoncom.IID_IDropTarget
            )
        )

    def add_tracks(self, tracks):
        for track in tracks:
            if track not in self.track_list:
                p = ttk.Label(self.frame, text=str(track))
                print(p['text'])
                p.pack() # This is the offending line
        self.track_list.update(tracks)


class UI(ttk.Frame):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.tracks = TrackList(
            self,
            labelwidget=ttk.Label(text='Track List')
        ).pack()


class App(tk.Tk):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.ui = UI(self, name='ui')
        self.ui.pack(fill='both', expand=True)
        self.mainloop()


App()

dnd.py

from pathlib import Path

from pythoncom import TYMED_HGLOBAL, IID_IDropTarget
from pywintypes import com_error
from win32con import CF_HDROP
from win32comext.shell import shell
from win32com.server.policy import DesignatedWrapPolicy


def get_drop_paths(drop):
    try:
        data = CF_HDROP, None, 1, -1, TYMED_HGLOBAL
        path_data = drop.GetData(data)
    except com_error:
        return
    paths = []
    query = shell.DragQueryFile
    for i in range(query(path_data.data_handle, -1)):
        fpath = Path(query(path_data.data_handle, i))
        if fpath.is_dir():
            paths += list(fpath.iterdir())
        else:
            paths.append(fpath)

    return paths


class DropTarget(DesignatedWrapPolicy):
    _public_methods_ = ['DragEnter', 'DragOver', 'DragLeave', 'Drop']
    _com_interface_ = [IID_IDropTarget]

    def __init__(self, widget):
        self._wrap_(self)
        self.widget = widget

    def DragEnter(self, *args):
        ...

    def DragOver(self, *args):
        ...

    def DragLeave(self, *args):
        ...

    def Drop(self, data_object, *args):
        paths = get_drop_paths(data_object)
        self.widget.add_tracks(paths)

我尝试研究这个问题,但没有什么可展示的为了我的努力。我发现的一点点表明它与 C API 调用(我对此知之甚少)有关 - 也许与 win32api 有关 - 并且主要发生在 GUI 中(如我的情况)。


The error you're getting, "Fatal Python error: PyEval_RestoreThread: the function must be called with the GIL held", indicates a clash between Python's Global Interpreter Lock (GIL) and multi-threaded operations, likely triggered by the interaction between your Tkinter application, drag-and-drop functionality, and potentially the pythoncom library.

Here's a breakdown of the issue and how to approach it:

Understanding the GIL

  • Python's GIL is a mechanism that allows only one thread to execute Python bytecode at a time within a single process. This simplifies many aspects of CPython (the most common Python implementation) but can limit true parallelism in CPU-bound tasks.
  • C extensions to Python (like those in tkinter or pythoncom ) often need to interact with Python objects. To ensure safety and prevent data corruption, the GIL must be held during these interactions.

The Problem

The error message implies that a function that requires the GIL is being called when the GIL is released. This suggests a thread is attempting to access or modify Python objects from a context where it's not allowed. This is likely happening because:

  1. Drag-and-drop is asynchronous: The drag-and-drop operation might be running in a separate thread provided by the operating system.
  2. Callback execution: When a drop occurs, your DropTarget.Drop method is called. This method, in turn, calls self.widget.add_tracks(paths) , which interacts with Tkinter widgets.
  3. Tkinter's threading model: Tkinter (in CPython) is generally not thread-safe. Modifying Tkinter widgets from a thread other than the main thread can lead to unpredictable behavior and crashes.

Solution: Enforcing Thread Safety

The solution involves ensuring that any interaction with Tkinter widgets from the drag-and-drop callback happens on the main thread. Here's how you can modify your code:

import tkinter as tk
from tkinter import ttk
import pythoncom
from dnd import DropTarget

# ... [Your other classes: ScrollFrame, UI] 

class TrackList(ScrollFrame):
    # ... [your existing code] 

    def add_tracks(self, tracks):
        def update_track_list():  # Function to be executed on the main thread
            for track in tracks:
                if track not in self.track_list:
                    p = ttk.Label(self.frame, text=str(track))
                    print(p['text'])
                    p.pack()
            self.track_list.update(tracks)

        self.after_idle(update_track_list)  # Schedule execution on the main thread 

# ... [Rest of your code: App class] 

Explanation

  1. update_track_list function: We've encapsulated the code that modifies your Tkinter widgets within this function.
  2. self.after_idle(update_track_list) : This line is crucial. It uses Tkinter's after_idle method to schedule the update_track_list function to be executed on the main thread when the application is idle. This ensures that the widget modifications are handled safely.

Important Considerations

  • Error Handling: Always wrap external library calls and thread interactions in robust error handling blocks to gracefully handle any potential exceptions.
  • Thread Management: If you're dealing with more complex multi-threaded scenarios, consider using Python's threading module or a higher-level library like concurrent.futures to manage threads more effectively.

By ensuring that interactions with Tkinter happen on the main thread, you can avoid the GIL-related errors and maintain the stability of your application.

标签:python,tkinter,winapi
From: 78780456

相关文章

  • 如何使代码格式再次适用于 Python(Mac 上的 Visual Studio Code)?
    在Mac上,Option+Shift+F现在会显示“没有安装用于‘python’文件的格式化程序”。消息框:我尝试安装这个插件,但没有看到这种情况的变化:我已经为Python安装了这两个插件:但是正如@starball提到的,它可能已经减少了支持现在。......
  • 无法在 python 中安装 pip install expliot - bluepy 的 Building Wheel (pyproject.t
    在此处输入图像描述当我尝试在Windows计算机中通过cmd安装pipinstallexpliot包时,我收到2个错误名称×Buildingwheelforbluepy(pyproject.toml)didnotrunsuccessfully.│exitcode:1**AND**opt=self.warn_dash_deprecation......
  • python 用单斜杠-反斜杠替换url字符串中的双斜杠
    我的URL包含错误的双斜杠(“//”),我需要将其转换为单斜杠。不用说,我想保持“https:”后面的双斜杠不变。可以在字符串中进行此更改的最短Python代码是什么?我一直在尝试使用re.sub,带有冒号否定的正则表达式(即,[^:](//)),但它想要替换整个匹配项(包括前面......
  • 如何使用 Selenium Python 搜索 Excel 文件中的文本
    我有一些数据在Excel文件中。我想要转到Excel文件,然后搜索文本(取自网站表),然后获取该行的所有数据,这些数据将用于在浏览器中填充表格。示例:我希望selenium搜索ST0003然后获取名称,该学生ID的父亲姓名,以便我可以在大学网站中填写此信息。我想我会从网站......
  • Python 套接字请求在很多情况下都会失败
    我在python中尝试了超过5种不同的方法,尽管人们说它在其他论坛上有效,但所有这些方法都惨遭失败。importsocketmessage="test"clientsocket=socket.socket(socket.AF_INET,socket.SOCK_STREAM)clientsocket.connect(('1.1.1.1',80))clientsocket.send(mes......
  • Python 网络套接字
    我一直尝试通过Python访问该网站的websocket,但是需要绕过CloudFlare,现在我尝试通过cookie进行绕过,但是这不起作用。我已经尝试在没有cookie的情况下执行此操作,但这也不起作用。importwebsocketimportbase64importosdriver=selenium.webdriver.Firefox()driver.ge......
  • 如何在Python中使用Selenium提取data-v-xxx?
    因为我想查看每个class='num'内的文本是否大于0。如果测试通过,那么我需要获取venuen-name内的文本。我观察到,data-v是相同的。所以我的方法是获取相同的data-v-<hashvalue>来查找场地名称。我尝试了不同的方法来提取,但仍然无法提取。有什么建议吗?这是DOM<div......
  • Python:添加异常上下文
    假设我想提出一个异常并提供额外的处理信息;最好的做法是什么?我想出了以下方法,但对我来说有点可疑:definternal_function():raiseValueError("smellysocks!")defcontext_function():try:internal_function()exceptExceptionase:......
  • 【视频】Python遗传算法GA优化SVR、ANFIS预测证券指数ISE数据-CSDN博客
    全文链接:https://tecdat.cn/?p=37060本文旨在通过应用多种机器学习技术,对交易所的历史数据进行深入分析和预测。我们帮助客户使用了遗传算法GA优化的支持向量回归(SVR)、自适应神经模糊推理系统(ANFIS)等方法,对数据进行了特征选择、数据预处理、模型训练与评估。实验结果表明,这些方法......
  • Python学习笔记42:游戏篇之外星人入侵(三)
    前言在之前我们已经创建好了目录,并且编写好了游戏入口的模块。今天的内容主要是讲讲需求的分析以及项目各模块的代码初步编写。在正式编写代码前,碎碎念几句。在正式编写一个项目代码之前,实际是有很多工作要做的。就项目而言,简单的定项,需求对齐,项目架构设计,实际的代码编写,......