You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#代码加载新窗体触发System.OutOfMemoryException问题求助

System.OutOfMemoryException异常排查与解决

异常信息

System.OutOfMemoryException: 'Out of memory.'
An unhandled exception of type 'System.OutOfMemoryException' occurred in System.Drawing.dll

问题背景

加载新窗体时触发上述异常,调试显示当前托管内存占用仅37MB,多次尝试修复无果,相关代码如下:

相关代码

public static async Task<List<Meni>> RetrieveAll()
{
    using (var handler = new LoggingHttpClientHandler())
    using (var client = new HttpClient(handler))
    {
        client.Timeout = TimeSpan.FromSeconds(5);

        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        Console.WriteLine("Sending request");

        var response = await client.GetAsync($"https://apex.oracle.com/pls/apex/student12345/Meni/Get");

        Console.WriteLine("Request sent. Awaiting response...");

        if (Convert.ToInt32(response.StatusCode) == 200)
        {
            JObject menijson = JObject.Parse(await response.Content.ReadAsStringAsync());
            JArray meniji = (JArray)menijson["items"];

            List<Meni> list = meniji.ToObject<List<Meni>>();

            foreach (Meni meni in list)
            {
                await meni.dodajjela();
            }

            return list;
        }
        else
        {
            MessageBox.Show("Greška pri prustupanju popisa menija", "Error!", MessageBoxButtons.OK, MessageBoxIcon.Error);
            return null;
        }
    }
}

private async Task dodajjela()
{
    using (var handler = new LoggingHttpClientHandler())
    using (var client = new HttpClient(handler))
    {
        client.Timeout = TimeSpan.FromSeconds(5);

        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        Console.WriteLine("Sending request");

        var response = await client.GetAsync($"https://apex.oracle.com/pls/apex/student12345/Meni/GetRelated?men={this.id_menija}");

        Console.WriteLine("Request sent. Awaiting response...");

        if (Convert.ToInt32(response.StatusCode) == 200)
        {
            JObject jelajson = JObject.Parse(await response.Content.ReadAsStringAsync());
            JArray jela = (JArray)jelajson["items"];

            foreach (var jel in jela)
            {
                var resp =  await Jela.Retrieve(jel["idjela"].ToString());
                this.jelas.Add(resp);
            }
        }
    }
}

public static async Task<Jela> Retrieve(string id)
{
    using (var handler = new LoggingHttpClientHandler())
    using (var client = new HttpClient(handler))
    {
        client.Timeout = TimeSpan.FromSeconds(5);

        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        Console.WriteLine("Sending request");

        var response = await client.GetAsync($"https://apex.oracle.com/pls/apex/student12345/Meni/Jelo?je={id}");

        Console.WriteLine("Request sent. Awaiting response...");

        if (Convert.ToInt32(response.StatusCode) == 200)
        {
            JObject jelajson = JObject.Parse(await response.Content.ReadAsStringAsync());
            jelajson.Remove("links");
            return CreateObj(jelajson);
        }

        return null;
    }
}

private static Jela CreateObj(JObject objekt)
{
     Image slikajelo = null;

     using (WebClient client = new WebClient())
     {
         byte[] slikabiteovi = client.DownloadData($"https://apex.oracle.com/pls/apex/student12345/Meni/image?br={objekt.Property("id_jelo").Value.ToString()}");

         using (MemoryStream ms = new MemoryStream(slikabiteovi))
         {
             slikajelo = Image.FromStream(ms);
         }
     }

     Jela jelo = new Jela
     {
         id_jela = objekt.Property("id_jelo").Value.ToString(),
         slika= slikajelo,
         ime_jela= objekt.Property("ime_jela").Value.ToString()
     };
                   
     return jelo;
}

异常原因分析

  1. GDI+资源泄漏而非托管内存不足:异常来自System.Drawing.dll,说明是非托管的GDI+资源耗尽,而非托管内存不足(这就是为什么托管内存显示37MB却报错)。CreateObj方法中,Image.FromStream(ms)创建的Image会依赖外部MemoryStream,当Stream被using释放后,Image的内部资源会处于不稳定状态;同时Jela对象的Image属性未被主动释放,大量加载后GDI+资源被耗尽。
  2. HttpClient重复创建:每个请求都新建HttpClient和Handler,会导致TCP连接池耗尽,额外消耗系统资源,间接加剧GDI+资源压力。
  3. 未优化图片加载:直接加载原图,若图片尺寸过大,会占用更多GDI+资源。

解决方法

1. 修复Image资源管理

修改CreateObj方法,复制Image以解除对Stream的依赖,同时让Jela类实现IDisposable接口释放Image资源:

private static Jela CreateObj(JObject objekt)
{
    Image slikajelo = null;

    using (WebClient client = new WebClient())
    {
        byte[] slikabiteovi = client.DownloadData($"https://apex.oracle.com/pls/apex/student12345/Meni/image?br={objekt.Property("id_jelo").Value.ToString()}");

        using (MemoryStream ms = new MemoryStream(slikabiteovi))
        {
            // 复制Image,避免依赖外部Stream
            using (var tempImage = Image.FromStream(ms))
            {
                slikajelo = new Bitmap(tempImage);
            }
        }
    }

    Jela jelo = new Jela
    {
        id_jela = objekt.Property("id_jelo").Value.ToString(),
        slika = slikajelo,
        ime_jela = objekt.Property("ime_jela").Value.ToString()
    };
               
    return jelo;
}

// Jela类实现IDisposable
public class Jela : IDisposable
{
    public string id_jela { get; set; }
    public Image slika { get; set; }
    public string ime_jela { get; set; }
    public List<Jela> jelas { get; set; }

    private bool _disposed = false;

    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                // 释放托管资源
                slika?.Dispose();
            }
            _disposed = true;
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

使用Jela对象后,需调用jelo.Dispose()释放资源,或在循环中用using包裹:

foreach (var jel in jela)
{
    using(var resp = await Jela.Retrieve(jel["idjela"].ToString()))
    {
        this.jelas.Add(resp);
    }
}

2. 复用HttpClient实例

避免每次请求新建HttpClient,改为静态单例复用:

// 在对应类中定义静态HttpClient
private static readonly HttpClient _httpClient;

static YourClassName() // 替换为实际类名
{
    var handler = new LoggingHttpClientHandler();
    _httpClient = new HttpClient(handler);
    _httpClient.Timeout = TimeSpan.FromSeconds(5);
    _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}

之后在RetrieveAll、dodajjela、Retrieve方法中直接使用_httpClient,移除原有的using创建代码。

3. 优化图片加载(可选)

若图片尺寸过大,加载时进行缩放,减少GDI+资源占用:

using (var tempImage = Image.FromStream(ms))
{
    // 缩放到目标宽度,保持比例
    int targetWidth = 200;
    float scale = (float)targetWidth / tempImage.Width;
    int targetHeight = (int)(tempImage.Height * scale);
    slikajelo = new Bitmap(tempImage, targetWidth, targetHeight);
}

内容的提问来源于stack exchange,提问作者DorianK

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 19:07:09