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

标签 c# .net async-await winrt-async stream-socket-client

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

我是否使用普通的旧 while (true)async Task 中循环,或使用 self 发布 ActionBlock<T>使用 TPL 数据流(根据 this answer ),结果是相同的。

如果我消除从套接字读取并专注于写入,我能够进一步隔离问题: 我是否使用 DataWriter.StoreAsync方法或更直接的StreamSocket.OutputStream.WriteAsync(IBuffer buffer) ,问题依旧。此外,添加 .AsTask()这些都没有区别。

即使垃圾收集器运行,这些Task<uint32>永远不会从堆中移除。所有这些任务都已完成 ( RanToCompletion ),没有错误或任何其他属性值表明“尚未准备好回收”。

关于 this page 的问题似乎有提示(从托管世界到非托管世界的字节数组会阻止内存释放),但规定的解决方案似乎很明显:解决这个问题的唯一方法是用 C++/CX 编写所有通信逻辑。我希望这不是真的;当然,其他 C# 开发人员已经成功地实现了没有内存泄漏的连续高速网络通信。 Microsoft 肯定不会发布仅在 C++/CX 中没有内存泄漏的 API

编辑

根据要求,一些示例代码。我自己的代码有太多层,但是可以用 this Microsoft sample 观察到一个更简单的例子。 .我做了一个简单的修改,循环发送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();
        }
    }
}

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

编辑#2 运行我的应用程序(使用紧密循环发送/接收)3 小时后,我可以看到肯定 有问题:50 万个任务实例,它们从未被 GC 处理过,以及应用程序的进程内存从最初的 46 MB 增加到 105 MB。显然这个应用程序不能无限期地运行。 但是...这仅适用于在调试 模式下运行。如果我在 Release 模式下编译我的应用程序,部署并运行它,则不会出现内存问题。我可以让它整夜运行,很明显内存管理得当。 结案。

最佳答案

there are 86 instances. After pressing it a 2nd time: 129 instances.

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

Task 听起来是一个非常昂贵的对象,它物超所值,并且涉及一个线程,这是您可以创建的最昂贵的操作系统对象。但事实并非如此,Task 对象实际上是一个微不足道的对象。在 32 位模式下只占用 44 个字节,在 64 位模式下占用 80 个字节。真正昂贵的资源不属于 Task,线程池管理器负责处理。

这意味着您可以在对 GC 堆施加足够的压力以触发收集之前创建很多 Task 对象。大约 47,000 它们填充 32 位模式下的 gen #0 段。服务器上的数量更多,数十万个,它的段要大得多。

在您的代码片段中,任务对象是您实际创建的唯一对象。因此,您的 for(;;) 循环几乎没有循环到足以看到 Task 对象的数量减少或限制的程度。

所以这是一个常见的故事,对 .NET Framework 存在泄漏的指控,尤其是在运行数月的服务器式应用程序中大量使用的这些基本对象类型上,永远被高度夸大。反复猜测垃圾收集器总是很棘手,您通常只能通过让您的应用运行数月并且从未因 OOM 失败而获得信心。

关于c# - 为什么在循环中使用 StreamSocket 会导致内存泄漏?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32960197/

相关文章:

c# - 连接到 OpenSSL 服务器时为 "Call to SSPI failed"

javascript - 需要在http响应后运行该变量

javascript - 找到合适的时机来访问根据异步调用的数据创建的 DOM 元素

c# - 驱动器号与 DevicePath

c# - 如何在带有图像的面板上绘制点

c# - 避免项目之间双向通信的循环引用

c# - 有没有办法为 EF 模型自动创建 CRUD(目前是 DB First)

c# - Visual Studio 2015 - 添加对类库 C# ASP.NET 的引用

c# - 我应该把 KeepAlive 放在我的 finally block 中吗?

rust - Async Rust 中每个 Future 的不同 Context 或 Waker