【问题标题】:How to properly finalize/cleanup pythonnet CLR objects?如何正确完成/清理 pythonnet CLR 对象?
【发布时间】:2020-11-25 13:51:25
【问题描述】:

我正在使用 python 通过提供的 API 与某个软件进行通信。由于 API 代码是用 C# 编写的,我使用 pythonnet 导入 DLL 并随后使用它。这样做是有益的,例如优化代码时使用 Jupyter Lab 或 Jupyter Notebook,因为您可以轻松比较代码和软件中的结果。但是,我遇到了清理问题。 API 要求您通过运行以下代码来建立连接

import clr
clr.AddReference('API')
api = __import__('API', globals(), locals(), [], 0)
connection = api.connection()
app = connection.connect()

现在您可以使用app 与软件进行通信。我的问题的主要原因是您只能在 CLR 中拥有一个应用程序。如果你想创建一个新的,你应该调用app.close(),然后调用newapp = connection.connect()。当您创建 newapp 而不调用 app.close() 时会发生什么没有明确定义。我不确定 C# 将如何处理这个问题,它会覆盖内存中的appapp 现在还会指向newapp,还是其他什么?有了这个,我就更不确定python+CLR是怎么处理的了。

为了确保您始终使用正确连接的应用程序,我创建了一个类,它只允许存在一个 app 实例。此限制是通过 API 评估 connection.Alive 来实现的,当应用程序已生成但尚未正确关闭时,该限制为 True。该类类似于:

class APIWrapper:
    def __init__(self, api):
        self.API = api
        self.Connection = api.connection()
   
    def connect():
        if self.Connection.Alive:
            raise RunTimeError('Only one live application is allowed at runtime')

        app = self.Connection.connect()
        return app

虽然这工作正常,但当我不小心做了类似的事情时,我的问题就出现了:

wrap = APIWrapper()
wrap.connect()

这样做时,应用程序会上线,并且 wrap.Connection.Alive 的计算结果为 True。但是,由于我没有将 wrap.connect() 的返回值分配给变量,因此我无法使用 app.close() 将其关闭。例如,如果我这样做:

wrap = APIWrapper()
print(wrap.Connection.Alive)  # -> False
app = wrap.connect()
print(wrap.Connection.Alive)  # -> True
app.close()
print(wrap.Connection.Alive)  # -> False
wrap.connect()
print(wrap.Connection.Alive)  # -> True

我无法再关闭连接。我考虑过更改类,以便将wrap.connect() 绑定到wrap.App 并允许通过属性进行访问。这将解决丢失应用程序的问题,但我宁愿不必为了代码可读性而连续调用 wrap.App。另外,我只是想知道是否有适当的方法来处理这些最终确定问题?

【问题讨论】:

    标签: python c# api code-cleanup python.net


    【解决方案1】:

    首先,如果调用wrap.connect() 而不在任何地方存储返回值是问题所在,那么有一个简单的解决方案:不要这样做!看起来连接是一种资源,所以你必须跟踪它,以便在时机成熟时正确释放它。

    在您的示例中,当有人再次调用connect() 时,先前创建的连接会发生什么情况?

    其次,在 Python 中有两种方法可以显式地跟踪资源:

    1. with statements + context managers(强烈推荐)。在这种情况下,您需要在包装器上实现上下文管理器。
    2. __del__ 函数,您可以定义,将在不再需要对象时调用。这个你应该避免,因为它会在任意时间执行,这意味着当你尝试创建一个新连接时,旧的连接可能仍然存在,因为 Python 还没有意识到它应该调用 __del__

    另一种选择是制作a singleton

    【讨论】:

    • 我同意我不应该这样做,我问这个问题的主要原因是我正在为 API 编写一个包装器,并希望它尽可能健壮。通过这种方式,它允许与 API 进行动态交互,并确保您在不小心调用 wrap.connect 时不必重新启动所有内容。我还查看了__del__ 以及实现weakref.finalize__del__ 不起作用,因为连接仍然存在于 clr 中,而 weakref.finalize() 也没有。因此,我以不同的方式实现它。 (但会看单例!)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-15
    • 2018-10-14
    相关资源
    最近更新 更多