C# 如何避免异步方法上的OutOfMemoryException,每个方法都会消耗大量内存?

C# 如何避免异步方法上的OutOfMemoryException,每个方法都会消耗大量内存?,c#,asynchronous,memory,async-await,out-of-memory,C#,Asynchronous,Memory,Async Await,Out Of Memory,我试图处理一些在阅读时被显著扩展的项目列表。我在异步函数中处理它们,所以在某个时候,可能会有太多函数运行并抛出OutOfMemoryException 我在考虑根据一些硬限制来限制正在运行但未完成的同时任务的数量。但我无法真正预测有多少任务太少/太多,因为每个项目的扩展可能不同 是否有一些聪明的方法可以避免在不引入硬限制的情况下出现异常 引发该行为的示例: void Main() { Int32 million = 1000000; List<Tas

我试图处理一些在阅读时被显著扩展的项目列表。我在异步函数中处理它们,所以在某个时候,可能会有太多函数运行并抛出OutOfMemoryException

我在考虑根据一些硬限制来限制正在运行但未完成的同时任务的数量。但我无法真正预测有多少任务太少/太多,因为每个项目的扩展可能不同

是否有一些聪明的方法可以避免在不引入硬限制的情况下出现异常

引发该行为的示例:

void Main()
{        
    Int32 million = 1000000;    
    List<Task> tasks = new List<Task>();
    for (Int32 i = 0; i < 20; i++)
    {
        Task t = Do(million);
        tasks.Add(t);
    }   
    Task.WaitAll(tasks.ToArray());
}


public static async Task Do(Int32 count)
{
    //I tried 'await Task.Yield()' here; didn't help.

    //consume memory
    StringBuilder dataBuilder = new StringBuilder();
    for (Int32 i = 0; i < count; i++)
    {
        dataBuilder.Append(Guid.NewGuid().ToString());
    }
    String data = dataBuilder.ToString();
    Console.WriteLine($"here [{data.Length}]");
    //do I/O
    await Task.Delay(10000);
}
void Main()
{        
3200万整数=1000000;
列表任务=新列表();
对于(Int32 i=0;i<20;i++)
{
任务t=Do(百万);
任务。添加(t);
}   
Task.WaitAll(tasks.ToArray());
}
公共静态异步任务Do(Int32计数)
{
//我在这里尝试了“wait Task.Yield()”;没有帮助。
//消耗内存
StringBuilder dataBuilder=新建StringBuilder();
对于(Int32 i=0;i
使用流式而不是巨型缓冲区式代码

public static async Task Do(Int32 count)
{
    //I tried 'await Task.Yield()' here; didn't help.

    //consume memory
    for (Int32 i = 0; i < count; i++)
    {
        Console.Write(Guid.NewGuid().ToString());
    }

    //do I/O
    await Task.Delay(10000);
}
公共静态异步任务Do(Int32计数)
{
//我在这里尝试了“wait Task.Yield()”;没有帮助。
//消耗内存
对于(Int32 i=0;i

这样做的目的不是缓冲所有输出并在最后发送,而是在创建过程中发送(到文件、网络、帧缓冲区等)。

为什么不使用
任务。运行
?按照这种编码方式,您在屈服之前在原始线程中创建了一个字符串
await
不会让任何东西通过魔法异步运行,它只允许您使用
await
关键字来等待已经异步的操作。对于OOM异常,您是以32位还是64位运行?分配如此大的对象可能会导致内存碎片,分配程序在一段时间后找不到一个可用的内存块。更糟糕的是,通过逐位添加数据,StringBuilder必须在每次填充前一个缓冲区时分配新的缓冲区。重新分配是通过分配一个两倍于旧缓冲区的缓冲区并复制数据来执行的。这可能会为一个大字符串浪费大量内存。由于您已经知道最终字符串的大小,请在创建SringBuilder时指定它,例如
newstringbuilder(36*count)
任务。收益率
收益率。它没有安排任何运行。至于内存问题,您分配了20个字符串,36M字节长,每个字符串至少有10个重新分配-这是很多字符串。GC没有机会运行,即使它运行了,它也不能足够快地清理这么多大型对象。难怪你最后会有一个OOM。内存碎片足以导致内存不足有两种解决方案:1。多买一点,或者2点。少用。在这种情况下,我认为通过将算法重新编写为一个一次只处理一小部分数据的流处理器,可以获得更高的性能。典型的例子是一个字符串解析器,它一次只能在内存中保存一个字符。