首页 > 解决方案 > C# 应用程序结束时分配的内存不会被释放

问题描述

我有一个围绕 Python 模块的 C++ 包装器,它在 C++ 中完美运行。那就是没有任何内存泄漏。我在 C 中公开了 C++ 功能,并在 C# 应用程序中使用了它。但是,我注意到每当我结束 C# 应用程序时,分配的内存都不会被释放!

具有讽刺意味的是,一切都是在 Python 中完成的,而在 C++ 中没有内存分配,它只是一系列调用 Python 对应物的方法,而在 Python 中,所有内存都由解释器管理。Python 模块基本上运行一个无限循环,其中使用网络摄像头提要,处理图像并将其发送回客户端(C#、C++)。Python中有一个Stop方法,它简单地设置一个值,在python中结束这个循环,所以当那个循环结束时,实际上一切都结束了,因此被释放了。

旁注:
您看到的内存与加载到内存中的模型有关,并且该模型由Pytorch(Python 中的深度学习框架)管理,因此在 Python 部分,我们只需要调用其他托管代码。

C++ 只是调用这些方法,我在 Python 或 C++ 中没有任何问题,但是当涉及到 C# 时,分配的内存不会被释放!

万一这很重要,这是我DLLImport对 C# 的陈述:

[DllImport(@"Core_DLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int Initialize(bool showFeed);

[DllImport(@"Core_DLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void Start(bool async);

[DllImport(@"Core_DLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void Stop();

[DllImport(@"Core_DLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void SetCpuAffinity(int mask);

[DllImport(@"Core_DLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern bool GetVideoFeedDbgStatus();

[DllImport(@"Core_DLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void SetVideoFeedDbgStatus(bool status);

public delegate void CallbackDelegate(bool status, string message, IntPtr img, int rows, int cols);
[MethodImplAttribute(MethodImplOptions.InternalCall)]

[DllImport(@"Core_DLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void AddCallback(IntPtr fn);

[DllImport(@"Core_DLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void RemoveCallback(IntPtr fn);

[DllImport(@"Core_DLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern  IntPtr GetCallbacks_str();

这是 C# 回调:

int op=0;
public void callback01(bool status, string id, IntPtr img_ptr, int rows, int cols)
{
     this.status = status;
     this.id = id;
     int[] sizes = { rows, cols };

     img = new Mat(sizes, MatType.CV_8UC3, img_ptr);
     // this is to retain the image, as the callback is so fast
     // the time c# will draw it, it will be invalidated and thus you'd get
     // distorted image. sowe make a deep copy here so we can work with images
     // at our own pace!
     img2 = new Mat();
     img.CopyTo(img2);

     //this allows to execute all interop calls under one thread
     switch (op)
     {
          case 1:
              Stop();
              //MessageBox.Show("done.");
              t.Abort();
              break;
          case 2:
              MessageBox.Show(GetVideoFeedDbgStatus().ToString());
              break;
          default:
              break;
      }
      //resetting the flag
      op = 0;
}

它是如何开始的:

Thread t;
private void btnRunService_Click(object sender, EventArgs e)
{
    tmrFetchStatus.Start();
    t = new Thread(new ThreadStart(() =>
    {
        RunService();
    }));
    t.IsBackground = true;
    t.Start();
}

void RunService()
{
    btnInit_Click(null, null);
    Start(chkboxAsync.Checked);
}

private void btnInit_Click(object sender, EventArgs e)
{
    Initialize(chkbxIShowFeed.Checked);
    SetUpCallback();
    tmrFetchStatus.Enabled = true;
}

这就是它的结束方式:

private void btnStop_Click(object sender, EventArgs e)
{
     op = 1;
}

C 暴露的方法是这样的:

extern "C"
{
    Core* core = nullptr;

    CORE_API int Initialize(bool showFeed)
    {
        return CreateHandle(showFeed) != nullptr ? 0 : 1;
    }

    CORE_API void Start(bool async)
    {
        core = reinterpret_cast<Core*>(GetHandle());
        core->Start(async);
    }

    CORE_API void Stop(void)
    {
        core = reinterpret_cast<Core*>(GetHandle());
        core->Stop();
    }

    CORE_API void AddCallback(CCallbackFn callback)
    {
        core->AddCallback_C_tmp(callback);
    }

    CORE_API void RemoveCallback(CCallbackFn callback)
    {
        core->RemoveCallback_C_tmp(callback);
    }

    std::string callbackLst;
    CORE_API const char* GetCallbacks_str(void)
    {
        core = reinterpret_cast<Core*>(GetHandle());
        auto results = core->GetCallbacks_C_tmp();

        // this is shared, so clear it each time. 
        callbackLst = "";
        for (auto pair : results)
        {
            callbackLst += pair.first + "\r\n";
        }

        //size_t size = callbackLst.size() * sizeof(char) + 1;
        //char* output = new char[size];
        //strcpy_s(output, size, callbackLst.c_str());

        return callbackLst.c_str();
    }

    CORE_API CCallbackFn* GetCallbacks()
    {
        core = reinterpret_cast<Core*>(GetHandle());
        auto results = core->GetCallbacks_C_tmp();
        size_t size = results.size() * sizeof(CCallbackFn) + 1;
        CCallbackFn* callback_list = new CCallbackFn[size];
        int i = 0;
        for (auto pair : results)
        {
            callback_list[i] = pair.second;
        }
        return callback_list;
    }

}

在使用/调用 Python 模块的 C++ 部分中:


    this->serviceUtilsModule = py::module::import("SomeModule");
    this->cls = this->serviceUtilsModule.attr("SomeClass");
    //...
    this->startFunc = this->obj.attr("start");
    this->startFuncAsync = this->obj.attr("start_async");
    this->stopFunc = this->obj.attr("stop");
...
}
//...
CORE_API void Core::Start(bool async)
{
    try
    {
        if (async)
        {
            this->startFuncAsync();
        }
        else
        {
            this->startFunc();
        }
    }
    catch (std::exception ex)
    {
        std::cout << ex.what() << std::endl;
    }
}

CORE_API void Core::Stop(void)
{
    this->stopFunc();
}

这可能是什么原因?我应该做什么我不是?这是一个小演示,展示了 C++ 应用程序和 C# 之间的区别(C++ 没有任何内存问题,而在 C# 中,我必须手动释放内存!):https ://imgur.com/a /eaW9AYT

这是第二个剪辑,显示了内部的 C# 应用程序(更多调试信息):https ://imgur.com/a/wL6UUOV

C++ 应用程序只需starts,然后在 10 秒后停止调用,当存在时释放所有内存。但是,在 C# 中,这不会发生,如您所见。

标签: c#

解决方案


我发现了这个问题。该问题似乎是由终止自身内部的线程引起的,该线程在其内部callback01运行,假设线程 2,我们试图终止这个线程:

...
     switch (op)
     {
          case 1:
              Stop();
              //MessageBox.Show("done.");
              t.Abort();
              break;
          case 2:
              MessageBox.Show(GetVideoFeedDbgStatus().ToString());
              break;
          default:
              break;
      }

Stop 确实停止了 python 循环,但它并没有尽快释放内存!我相信是因为没有卸载python模块,python对象仍然存在,因此保留了它的内存。但是,关闭 C# 应用程序会释放内存。
由于线程被标记为背景,关闭应用程序也应该终止它。

旁注:
我想知道为什么我在 C# 中没有遇到任何异常!


推荐阅读