2

我已经追踪了很远的泄漏,但我自己似乎无法理解(修复)它。我首先使用 ANTS 内存分析器来确保我的代码实际上是在堆叠内存。它从使用 25 MB 开始,但在一个小时左右的时间内使用超过 100 MB。我正在为其编写此代码的朋友实际上一直在使用这个有问题的程序,他用了整个 18 GB 的内存并得到了内存不足的异常。

泄漏部分对程序来说并不重要,但如果没有 RefreshSessions() 方法,它几乎没有用处。

我一直在从 Code Project扩展项目Vista Core Audio API Master Volume Control 。

这是似乎泄漏的部分。通过不使用它进行测试,然后它不会泄漏。

更新:

public void RefreshSessions()
    {
        Marshal.ThrowExceptionForHR(_AudioSessionManager.GetSessionEnumerator(out _SessionEnum));
        _Sessions.Refresh(_SessionEnum);
    }

(从这里删除了类代码)

我没有写太多代码,所以我可能遗漏了一些东西,但如果需要更多细节,你可以实际下载源代码,或者我可以尽我所能回答。

(此处删除了不必要的代码)

使用这个简单的控制台应用程序测试了泄漏:

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            MMDeviceEnumerator DevEnum = new MMDeviceEnumerator();
            MMDevice device = DevEnum.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia);

            Console.ReadKey();
            int i = 0;
            while (i < 10000)
            {
                device.AudioSessionManager.RefreshSessions();
                i++;
            }
            Console.ReadKey();
        }
   }
}

更新 2

我想我把它修好了。必须运行一些更长的测试,但至少看起来内存使用情况已经稳定。这个想法来自 dialer,他找到了 C++ 泄漏的修复方法。

public void RefreshSessions()
        {
            _Sessions.Release(); //added this
            IAudioSessionEnumerator _SessionEnum;
            Marshal.ThrowExceptionForHR(_AudioSessionManager.GetSessionEnumerator(out _SessionEnum));
            _Sessions.Refresh(_SessionEnum);
        }

这是中的部分SessionCollection

public void Release()
        {
            Marshal.ReleaseComObject(_AudioSessionEnumerator);
        }

这不完全是建议的代码拨号器(我最终还是使用了它),但仍然如此。正如他所说,这可能不是实现这一目标的最佳方式,但我会继续使用它,因为它似乎对我的应用程序没有任何不利影响。

4

3 回答 3

2

另一个编辑

public void RefreshSessions()
{
    if (_SessionEnum != null)
    {
        Marshal.ReleaseComObject(_SessionEnum);
    }
    Marshal.ThrowExceptionForHR(_AudioSessionManager.GetSessionEnumerator(out _SessionEnum));
}

上面的代码显式释放了 SessionEnum 并修复了 C# 中的泄漏。不过,这可能应该以更好的方式处理。

编辑:

下面的 C++ 程序等效于您在循环测试程序中所做的。Releasefor 循环末尾的调用修复了泄漏。我今天需要去,也许你可以玩一会儿,试着自己解决。或者也许其他人可以找出并解释为什么 CLR 垃圾收集器不会Release在上面的 C# 程序中的某个时间点自动调用。

#include <stdio.h>
#include <tchar.h>
#include <audiopolicy.h>
#include <mmdeviceapi.h>

#define SAFE_RELEASE(p) { if ( (p) ) { (p)->Release(); (p) = 0; } }
#define CHECK_HR(hr) if (FAILED(hr)) { goto done; }

const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
const IID IID_IAudioSessionManager2 = __uuidof(IAudioSessionManager2);

int _tmain(int argc, _TCHAR* argv[])
{
    HRESULT hr;

    CoInitialize(0);

    IMMDeviceEnumerator *deviceEnum = 0;
    CHECK_HR(hr = CoCreateInstance(
        CLSID_MMDeviceEnumerator, NULL,
        CLSCTX_ALL, IID_IMMDeviceEnumerator,
        (void**)&deviceEnum));;

    IMMDevice *endpoint = 0;
    CHECK_HR(deviceEnum->GetDefaultAudioEndpoint(eRender, eMultimedia, &endpoint));

    getchar();

    // lazy initialization as found in MMDevice.AudioSessionManager..get
    IAudioSessionManager2 *m = 0;
    CHECK_HR(endpoint->Activate(IID_IAudioSessionManager2, CLSCTX_ALL, 0, (void **)&m));

    for (int i = 0; i < 100000; i++)
    {
        IAudioSessionEnumerator *sessEnum = 0;
        m->GetSessionEnumerator(&sessEnum);
        sessEnum->Release(); // leak
    }

    getchar();

    printf("success");
    return 0;

done:
    printf("failure");
    return 1;
}

老的

我的猜测

_AudioSessionManager.GetSessionEnumerator(out _SessionEnum)产生一个枚举器。当你调用构造函数SessionCollection(_SessionEnum)时, then_SessionEnum正在被枚举。每个枚举步骤都会检索一个实际的非托管对象。

如果它是一个值类型,那么它实际上会被复制到会话集合中(请记住,List(IEnumerable e)构造函数复制每个元素)。然后副本将被垃圾收集,但原始对象是从非托管代码中分配的并产生泄漏。如果是这种情况,您应该在调用 Collection 构造函数后立即使用一些不受管理的内存释放函数释放内存。

如果它是引用类型,它也不会被释放,因为内存中的实际对象没有被垃圾回收,因为它是从非托管代码中分配的。如果是这种情况,您需要在不再需要对象时使用非托管库函数释放它们的内存。

于 2013-03-28T21:35:08.753 回答
1

如果您有非托管代码,_Sessions 内存何时释放?如果您只是重新分配私有字段,则永远不会释放内存。

这是一个例子: http ://social.msdn.microsoft.com/forums/en-US/clr/thread/b2162d42-0d7a-4513-b02c-afd6cdd854bd

您需要使用 dll 的方法来释放内存(C++ 中的 delete[])

于 2013-03-28T21:30:26.580 回答
0

.NET 总是能够轻松泄漏内存 - 或者更确切地说,它会处理未收集的垃圾,因为 GC 认为它们正在使用中,所以它们永远不会被清理。最著名的事件是 DARPA 挑战小组,他们相信这种炒作,并认为漏洞存在于他们的 C 驱动程序代码中,可怜的人。

从那时起,出现了相当多的内存泄漏分析器。我认为Redgate 的 ANTS中最著名的一个,但还有很多其他的。运行您的应用程序,查看哪些对象受到欢迎,查看哪些对象引用了它们,在正确的位置放置一些代码以取消引用它们(例如,更多的 Dispose 和/或 using 语句)。

于 2013-04-25T20:44:13.430 回答