[研究]ASP.NET,System.OutOfMemoryException 問題改善
2025-03-28x
環境:Visual Studio 2022 + ASP.NET + WebForm + Web Application + C# + SQL Server 2019 + SQL Server Management Studio (SSMS) 19
ASP.NET, C#, WebForm, 下面程式在正式機出現 System.OutOfMemoryException,在測試機正常可以執行完,工作管理員檢查正式機還有 8 GB RAM,請教比較可能出現 記憶體用盡的地方和原因 ?
try { Response.Clear(); Response.Buffer = true; Response.Charset = ""; #region === Export === using (MemoryStream myMemoryStream = new MemoryStream()) { string dataText = ""; var wb = new XLWorkbook(); var ws = wb.Worksheets.Add("工作表1"); string[] columnNameArray = { "流水號", "產品ID"}; for (int i = 0; i < columnNameArray.Count(); i++) { ws.Cell(1, i + 1).Value = columnNameArray[i]; } string queryString = @"SELECT * FROM Table1"; using (SqlConnection connection = new SqlConnection( WebConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString)) { SqlCommand command = new SqlCommand(queryString, connection); connection.Open(); using (SqlDataReader reader = command.ExecuteReader()) { int rowIndex = 1; // 1 是標題列 while (reader.Read()) { rowIndex++; for (int i = 0; i < reader.FieldCount; i++) { dataText = reader[i].ToString(); dataText = dataText.Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(",", ",").Replace("'", "′").Replace(@"""", "”"); dataText = dataText.Replace("\a", "").Replace("\b", "").Replace("\t", "").Replace("\v", "").Replace("\f", "").Replace("\\", ""); ws.Cell(rowIndex, i + 1).Value = dataText; } } } Response.Clear(); Response.Buffer = true; Response.ContentEncoding = Encoding.GetEncoding(950); Response.AddHeader("Content-Disposition", "attachment;filename=\"" + outFileName); Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; wb.SaveAs(myMemoryStream); Response.BinaryWrite(myMemoryStream.ToArray()); Response.Flush(); Response.Close(); wb.SaveAs(dirFileName); Response.End(); Label_MSG1.ForeColor = System.Drawing.Color.Green; Label_MSG1.Text = "匯出成功。"; } } #endregion Export ODS } catch (Exception) { throw; } } |
********************************************************************************
ChatGPT 說:
你的程式在正式機出現 System.OutOfMemoryException,但測試機可以正常執行,這可能與正式機的記憶體使用情況、資料量大小或 GC (Garbage Collection) 行為不同有關。以下是幾個可能的記憶體耗盡點與原因:
1. 讀取大量資料導致記憶體爆掉
你的 SQL 查詢 SELECT * FROM Table1 沒有限制返回的行數,這可能會導致:
SqlDataReader 讀取過多資料,特別是 Table1 資料量很大時。
XLWorkbook (wb) 物件持有過多資料,佔用大量記憶體。
解決方案:限制查詢結果
string queryString = @"SELECT TOP 10000 * FROM Table1"; // 減少資料量
|
改用分批處理 例如,使用 OFFSET + FETCH NEXT 來分批載入資料。
********************************************************************************
2. MemoryStream 佔用過大記憶體
你的程式碼嘗試將 Excel 儲存到 MemoryStream (myMemoryStream),再將其轉為 Response.BinaryWrite(myMemoryStream.ToArray());,這會:
將整個 Excel 檔案載入記憶體。
當 myMemoryStream.ToArray() 被呼叫時,會分配一個新的 byte 陣列,可能導致額外的記憶體開銷。
解決方案:避免 ToArray(),改用 WriteTo(Response.OutputStream); 直接將 MemoryStream 內容寫入 Response:
|
********************************************************************************
3. Response.End() 可能導致未處理的物件佔用記憶體
Response.End(); 會拋出 ThreadAbortException,這可能導致:
物件 (wb, MemoryStream 等) 無法正常釋放。
GC 無法即時回收記憶體。
🔹 解決方案 改用 CompleteRequest():
|
********************************************************************************
4. XLWorkbook 物件沒有被正確釋放
EPPlus / ClosedXML (XLWorkbook) 會佔用大量記憶體,因為它在記憶體中維護整個 Excel 物件。
🔹 解決方案
顯式釋放 XLWorkbook
wb.Dispose();
|
完整修正建議:
using (XLWorkbook wb = new XLWorkbook()) { // Excel 生成過程 using (MemoryStream myMemoryStream = new MemoryStream()) { wb.SaveAs(myMemoryStream); myMemoryStream.Position = 0; Response.Clear(); Response.Buffer = true; Response.ContentEncoding = Encoding.GetEncoding(950); Response.AddHeader("Content-Disposition", $"attachment; filename={outFileName}"); Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; myMemoryStream.WriteTo(Response.OutputStream); Response.Flush(); HttpContext.Current.ApplicationInstance.CompleteRequest(); } } // wb 物件會自動 Dispose() |
這樣可以減少記憶體佔用並降低 OutOfMemoryException 發生的機率。
********************************************************************************
總結
正式機發生 OutOfMemoryException 可能的原因:
- SQL 回傳過大資料集
- MemoryStream.ToArray() 造成記憶體額外分配
- Response.End() 造成資源未釋放
- XLWorkbook 沒有被正確釋放
解法:
- 限制 SQL 回傳筆數或改為分批處理
- 直接寫入 Response.OutputStream,避免 ToArray()
- 改用 CompleteRequest() 而非 Response.End()
- 確保 XLWorkbook 使用 using 或 Dispose() 釋放資源
********************************************************************************
完整改過後 CODE
try { Response.Clear(); Response.Buffer = true; Response.Charset = ""; #region === Export === using (MemoryStream myMemoryStream = new MemoryStream()) { using (XLWorkbook wb = new XLWorkbook()) { var ws = wb.Worksheets.Add("工作表1"); // 設定欄位標題 string[] columnNameArray = { "流水號", "產品ID" }; for (int i = 0; i < columnNameArray.Length; i++) { ws.Cell(1, i + 1).Value = columnNameArray[i]; } string queryString = @"SELECT * FROM Table1"; using (SqlConnection connection = new SqlConnection( WebConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString)) { using (SqlCommand command = new SqlCommand(queryString, connection)) { connection.Open(); using (SqlDataReader reader = command.ExecuteReader()) { int rowIndex = 1; // 1 是標題列 while (reader.Read()) { rowIndex++; for (int i = 0; i < reader.FieldCount; i++) { string dataText = reader[i]?.ToString() ?? ""; // 過濾不必要的特殊字元 dataText = dataText.Replace("\r", "") .Replace("\n", "") .Replace("\t", "") .Replace(",", ",") .Replace("'", "′") .Replace(@"""", "”") .Replace("\\", ""); ws.Cell(rowIndex, i + 1).Value = dataText; } } } } } // 儲存到 MemoryStream wb.SaveAs(myMemoryStream); } // wb.Dispose() 自動釋放資源 myMemoryStream.Position = 0; // 設定 HTTP 回應標頭 Response.ContentEncoding = Encoding.GetEncoding(950); Response.AddHeader("Content-Disposition", $"attachment;filename=\"{outFileName}\""); Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; // 直接寫入 Response.OutputStream,避免 ToArray() 佔用記憶體 myMemoryStream.WriteTo(Response.OutputStream); // 確保資料完全發送 Response.Flush(); HttpContext.Current.ApplicationInstance.CompleteRequest(); } // myMemoryStream.Dispose() 自動釋放資源 Label_MSG1.ForeColor = System.Drawing.Color.Green; Label_MSG1.Text = "匯出成功。"; #endregion } catch (Exception ex) { // 記錄錯誤日誌(可選) Label_MSG1.ForeColor = System.Drawing.Color.Red; Label_MSG1.Text = "匯出失敗:" + ex.Message; } |
********************************************************************************
實際測試,效能似乎有改善,但是產生的 .xlsx 疑似有問題
Microsoft Excel
我們發現了...的部分內容有問題。您要我們盡可能嘗試復原嗎?如果您信任此活頁簿的來源,請按一下[是]。
待查測、、、
(完)
相關
沒有留言:
張貼留言