[研究]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:
wb.SaveAs(Response.OutputStream); Response.Flush(); Response.End();
|
********************************************************************************
3. Response.End() 可能導致未處理的物件佔用記憶體
Response.End(); 會拋出 ThreadAbortException,這可能導致:
物件 (wb, MemoryStream 等) 無法正常釋放。
GC 無法即時回收記憶體。
🔹 解決方案 改用 CompleteRequest():
Response.Flush(); HttpContext.Current.ApplicationInstance.CompleteRequest();
|
********************************************************************************
4. XLWorkbook 物件沒有被正確釋放
EPPlus / ClosedXML (XLWorkbook) 會佔用大量記憶體,因為它在記憶體中維護整個 Excel 物件。
🔹 解決方案
顯式釋放 XLWorkbook
完整修正建議:
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
我們發現了...的部分內容有問題。您要我們盡可能嘗試復原嗎?如果您信任此活頁簿的來源,請按一下[是]。
待查測、、、
(完)
相關