为什么在循环中使用StreamSocket会导致内存泄漏?

BCA*_*BCA 10 .net c# stream-socket-client async-await winrt-async

我正在开发一个C#,UWP 10解决方案,它使用快速,连续的读/写循环与网络设备进行通信.API提供的StreamSocket似乎工作得很好,直到我意识到存在内存泄漏:Task<uint32>堆中有一个累积,大约每分钟数百个.

无论我while (true)在内部使用普通旧循环async Task,还是使用ActionBlock<T>TPL Dataflow 自我发布(根据此答案),结果都是相同的.

如果我从套接字中消除读取并专注于写作,我能够进一步隔离问题:无论是使用DataWriter.StoreAsync方法还是更直接StreamSocket.OutputStream.WriteAsync(IBuffer buffer),问题依然存在.此外,添加.AsTask()到这些没有区别.

即使垃圾收集器运行,它们Task<uint32>也永远不会从堆中删除.所有这些任务都是完整的(RanToCompletion),没有任何错误或任何其他属性值,表明"还没有准备好回收".

这个页面上似乎有一个提示我的问题(从托管到非托管世界的字节数组阻止了内存的释放),但是规定的解决方案看起来非常明显:唯一的方法是编写所有通信逻辑C++/CX.我希望这不是真的; 当然,其他C#开发人员已经成功实现了连续的高速网络通信而没有内存泄漏.当然,微软不会发布一个只在C++/CX中没有内存泄漏的API

编辑

根据要求,一些示例代码.我自己的代码有太多层,但使用此Microsoft示例可以观察到一个更简单的示例.我做了一个简单的修改,在循环中发送1000次以突出显示问题.这是相关代码:

public sealed partial class Scenario3 : Page
{
    // some code omitted

    private async void SendHello_Click(object sender, RoutedEventArgs e)
    {
        // some code omitted

        StreamSocket socket = //get global object; socket is already connected

        DataWriter writer = new DataWriter(socket.OutputStream);

        for (int i = 0; i < 1000; i++)
        {
            string stringToSend = "Hello";
            writer.WriteUInt32(writer.MeasureString(stringToSend));
            writer.WriteString(stringToSend);
            await writer.StoreAsync();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在启动应用程序并连接套接字时,Task<UInt32>堆上只有一个实例.单击"SendHello"按钮后,有86个实例.第二次按下后:129个实例.

编辑#2 上运行我的应用程序之后(紧循环发送/接收)3小时,我可以看到,这里绝对一个问题:50万分任务的情况下,它从来没有得到GC'd,以及应用程序的进程内存从最初的玫瑰46 MB到105 MB.显然这个应用程序无法无限期运行. 但是 ......这仅适用于在调试模式下运行.如果我在发布模式下编译我的应用程序,部署并运行它,则没有内存问题.我可以让它一整晚都在运行,显然内存管理正常.案件结案.

Han*_*ant 10

有86个实例.第二次按下后:129个实例.

这完全正常.并强烈暗示这里真正的问题是你不知道如何正确地解释内存分析器报告.

任务听起来像一个非常昂贵的对象,它有很大的优势,并且涉及一个线程,这是你可以创建的最昂贵的操作系统对象.但事实并非如此,Task对象实际上是一个微不足道的对象.它在32位模式下只需要44个字节,在64位模式下只需要80个字节.真正昂贵的资源不属于Task,线程池管理器负责处理它.

这意味着您可以在GC堆上施加足够的压力来触发集合之前创建大量的Task对象.其中约有47,000个以32位模式填充第0代段.服务器上有更多,数十万,其细分市场要大得多.

在您的代码段中,Task对象是您实际创建的唯一对象.因此,你的for(;;)循环通常不会经常循环,以至于看不到Task对象的数量减少或限制.

所以这是通常的故事,.NET Framework的泄漏指控,特别是在运行了几个月的服务器式应用程序中大量使用的这些基本对象类型,永远被夸大了.双重猜测垃圾收集器总是很棘手,你通常只会让你的应用程序运行数月并且永远不会在OOM上失败而获得信心.