我有一个带按钮的页面。点击后,我从表中获取539,200行,并使用OpenXML SDK创建excel文件。它创建了超过50MB的文件,我通过WebClient.UploadFile方法在FTP上传。在整个过程中,IIS Worker进程CPU消耗率达到30%,内存利用率达到1.2 GB。整个过程大约需要10分钟才能在我的服务器上完成。完成后,浏览器(Firefox)需要额外的10分钟,并显示错误"连接已重置"。在上一次执行完成之前10分钟后,我看到在日志中开始了另一次执行。我确信没有其他人在使用该服务器。
我的问题
为什么在流程完成后内存利用率没有下降?我仔细处理了每一个&每个对象。甚至称为垃圾收集器。我不得不重新启动IIS以释放内存。我可以在代码中做什么?
根据我的日志,总执行时间在10分钟内完成,但浏览器仍然没有响应,它一直显示"正在连接......"。又过了大约10分钟,它出错了"连接被重置"。出了什么问题?
为什么我在上一次结束之前看到了另一个执行日志?是浏览器发送另一个请求还是IIS / ASP.Net疯了?
当使用Ajax请求执行时,我每隔~10分钟就会看到重复的日志条目,直到我重新启动IIS。发生了什么事?
重复的日志条目转换为重复执行相同的进程。我现在生气了。
我正在使用带有IIS 8的Windows Server 2012。
修改
获取数据
Function fun = new Function();
List<SqlParameter> para = new List<SqlParameter>();
para.Add(new SqlParameter() { ParameterName = "@IDs", SqlDbType = SqlDbType.NVarChar, Size = 4000, Value = "something" });
para.Add(new SqlParameter() { ParameterName = "@Fromdate", SqlDbType = SqlDbType.Date, Value = "2017-06-01" });
para.Add(new SqlParameter() { ParameterName = "@Todate", SqlDbType = SqlDbType.Date, Value = "2017-06-27" });
dsExcel = fun.GetDataSet("sp_GetData", para);
导出到Excel(带数据透视表)
private bool ExportDSToExcel(string destination)
{
LogUtil.LogInfo("Writing excel with rows: " + dsExcel.Tables[0].Rows.Count);
try
{
using (var spreadsheet = SpreadsheetDocument.Open(destination, true))
{
foreach (DataTable table in dsExcel.Tables)
{
WorkbookPart workbookPart = spreadsheet.WorkbookPart;
WorksheetPart worksheetPart = workbookPart.WorksheetParts.First();
string origninalSheetId = workbookPart.GetIdOfPart(worksheetPart);
WorksheetPart replacementPart =
workbookPart.AddNewPart<WorksheetPart>();
string replacementPartId = workbookPart.GetIdOfPart(replacementPart);
DocumentFormat.OpenXml.OpenXmlReader reader = DocumentFormat.OpenXml.OpenXmlReader.Create(worksheetPart);
DocumentFormat.OpenXml.OpenXmlWriter writer = DocumentFormat.OpenXml.OpenXmlWriter.Create(replacementPart);
while (reader.Read())
{
if (reader.ElementType == typeof(SheetData))
{
if (reader.IsEndElement)
continue;
writer.WriteStartElement(new SheetData());
DocumentFormat.OpenXml.Spreadsheet.Row headerRow = new DocumentFormat.OpenXml.Spreadsheet.Row();
writer.WriteStartElement(headerRow);
List<String> columns = new List<string>();
foreach (DataColumn column in table.Columns)
{
columns.Add(column.ColumnName);
Cell cell = new Cell();
cell.DataType = CellValues.String;
cell.CellValue = new CellValue(column.ColumnName);
writer.WriteElement(cell);
}
//End Row element writing
writer.WriteEndElement();
foreach (DataRow dsrow in table.Rows)
{
DocumentFormat.OpenXml.Spreadsheet.Row newRow = new DocumentFormat.OpenXml.Spreadsheet.Row();
writer.WriteStartElement(newRow);
foreach (String col in columns)
{
Cell cell = new Cell();
if ((dsrow[col].GetType().ToString().Contains(TypeCode.Int32.ToString()) || (dsrow[col].GetType().ToString().Contains(TypeCode.Decimal.ToString())) || (dsrow[col].GetType().ToString().Contains(TypeCode.Int64.ToString()))))
{
cell.DataType = CellValues.Number;
}
else
{
cell.DataType = CellValues.String;
}
cell.CellValue = new CellValue(dsrow[col].ToString()); //
writer.WriteElement(cell);
}
writer.WriteEndElement();
}
//End SheetData writing
writer.WriteEndElement();
}
else
{
if (reader.IsStartElement)
{
writer.WriteStartElement(reader);
}
else if (reader.IsEndElement)
{
writer.WriteEndElement();
}
}
}
reader.Close();
reader.Dispose();
writer.Close();
writer.Dispose();
Sheet sheet = workbookPart.Workbook.Descendants<Sheet>()
.Where(s => s.Id.Value.Equals(origninalSheetId)).First();
sheet.Id.Value = replacementPartId;
workbookPart.DeletePart(worksheetPart);
}
PivotTableCacheDefinitionPart ptp = spreadsheet.WorkbookPart.PivotTableCacheDefinitionParts.First();
ptp.PivotCacheDefinition.RefreshOnLoad = true;
ptp.PivotCacheDefinition.RecordCount = Convert.ToUInt32(dsExcel.Tables[0].Rows.Count); ptp.PivotCacheDefinition.CacheSource.WorksheetSource.Reference = "A1:" + IntToLetters(dsExcel.Tables[0].Columns.Count) + (dsExcel.Tables[0].Rows.Count + 1); ptp.PivotTableCacheRecordsPart.PivotCacheRecords.RemoveAllChildren();
ptp.PivotTableCacheRecordsPart.PivotCacheRecords.Count = 0;
spreadsheet.Save();
spreadsheet.Close();
spreadsheet.Dispose();
//GC.Collect();
//GC.WaitForPendingFinalizers();
}
LogUtil.LogInfo("Wrote excel");
return true;
}
catch (Exception ex)
{
return false;
}
}
上传到FTP
public void UploadFileToFtp(string file)
{
FileInfo fileInfo = new FileInfo(file);
using (WebClient client = new WebClient())
{
client.Credentials = ftpNetworkCredentials;
client.UploadFile(ftpUri + fileInfo.Name, "STOR", file);
client.Dispose();
}
LogUtil.LogInfo(file + " uploaded successfully");
}
按钮点击事件代码
protected void btnSubmit_Click(object sender, EventArgs e)
{
LogUtil.LogInfo("Getting data");
FillReportTable();
LogUtil.LogInfo("File upload is disabled");
string IOPath = Server.MapPath("~/Report-" + DateTime.Now.ToString("MM-dd-yyyy-hh-mm-ss") + ".xlsx");
if (System.IO.File.Exists(IOPath))
{
System.IO.File.Delete(IOPath);
}
System.IO.File.Copy(Server.MapPath("~/TempReport.xlsx"), IOPath);
ExportDSToExcel(IOPath);
if (Convert.ToBoolean(ConfigurationManager.AppSettings["ftpUpload"].ToString()))
{
UploadToFTP(IOPath);
}
else
{
LogUtil.LogInfo("File upload is disabled");
}
lblMessage.Text = "File uploaded successfully";
}
似乎内存利用率没有下降,因为服务器在完成上一个请求之前重复执行,因为浏览器每隔10分钟就会继续发送请求。我和小提琴手一起检查了但是它用轮询电话充斥了日志。无法彻底检查。 相同的代码需要相同的时间,并且在同一服务器上访问页面时工作正常。通过互联网做同样事情时会产生问题。我在工作时连接10mbps,在Azure云上连接VM。
答案 0 :(得分:1)
您可能遇到此行为的原因有很多,但一般来说,您需要跳过篮球才能让浏览器等待10分钟才能得到响应。相反,一般来说,您应该在此之前返回对浏览器的响应,并定期轮询您的应用程序以查看任务是否已完成。当然还有其他方法,比如使用websockets来启动进程并等待响应。
如果您的应用程序仍然具有对象的引用,它将保留在内存中,即使您已收集垃圾。如果没有看到您的代码,很难说这个引用可能在哪里。
如上所述,浏览器将在10分钟之前停止等待响应并关闭基础连接。
关闭非响应连接后,浏览器完全可能会自动尝试请求。
很难说没看到你的代码。
运行长时间运行任务的简单方法是使用Ajax触发它,正如您所说,已经完成并使用System.Threading.Tasks.Task
运行任务。您可以根据需要存储对任务的引用。然后可以使用Ajax轮询任务的状态以检查是否已完成。
听到一个样板实现,该实现使用服务器端的Generic Handler来运行一个需要1分钟才能完成的任务,以及一个使用JQuery使用Ajax启动任务并监控进度的HTML页面。
LongRunningTask.ashx
<%@ WebHandler Language="C#" Class="LongRunningTask" %>
using System;
using System.Web;
using System.Web.SessionState;
using System.Web.Script.Serialization;
using System.Threading.Tasks;
public class LongRunningTask : IHttpHandler, IRequiresSessionState
{
private const string INVALID = "Invalid value for m";
private const string SESSIONKEY = "LongRunningTask";
private const string STARTED = "Task Started";
private const string RUNNING = "Task Running";
private const string COMPLETED = "Task Completed";
public void ProcessRequest(HttpContext context)
{
HttpRequest request = context.Request;
string m = request.QueryString["m"];
switch (m)
{
case "start":
TaskRunner runner = new TaskRunner();
context.Session[SESSIONKEY] = runner.DoWork();
ShowResponse(context, STARTED);
break;
case "progress":
Task<int> t = (Task<int>)context.Session[SESSIONKEY];
ShowResponse(context, t.IsCompleted ? COMPLETED : RUNNING);
return;
default:
ShowResponse(context, INVALID);
break;
}
}
private void ShowResponse(HttpContext context, string message)
{
JavaScriptSerializer ser = new JavaScriptSerializer();
string json = ser.Serialize(message);
context.Response.ContentType = "text/javascript";
context.Response.Write(json);
}
public bool IsReusable
{
get
{
return false;
}
}
private class TaskRunner
{
public bool Finished { get; set; }
private Task<int> longTask;
public TaskRunner()
{
}
public Task<int> DoWork()
{
var tcs = new TaskCompletionSource<int>();
Task.Run(async () =>
{
// instead of the following line, launch you method here.
await Task.Delay(1000 * 60 * 1);
tcs.SetResult(1);
});
longTask = tcs.Task;
return longTask;
}
}
}
RunLongTask.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Run Long Task</title>
<script src="//code.jquery.com/jquery-2.2.4.min.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function () {
$('#runLongTask').click(function () { runLongTask(); })
});
function runLongTask() {
$.ajax
({
type: "GET",
url: "LongRunningTask.ashx?m=start",
dataType: 'json',
success: function (data) {
$('#status').html(data);
window.setTimeout(checkStatus, 1000);
}
});
}
function checkStatus() {
$.ajax
({
type: "GET",
url: "LongRunningTask.ashx?m=progress",
dataType: 'json',
success: function (data) {
$('#status').html(new Date() + ' ' + data);
if (data !== "Task Completed") {
window.setTimeout(checkStatus, 1000);
}
}
});
}
</script>
</head>
<body>
<div>
<input id="runLongTask" type="button" value="Run Long Task" title="Run Long Task" />
</div>
<div id="status"></div>
</body>
</html>
修改强>
查看新添加的代码,您可以集成相同的方法。
将Generic Handler添加到您的项目中。您可以删除任务运行器和开关&#34; start&#34;。
修改btnSubmit_Click以在任务中运行代码:
protected void btnSubmit_Click(object sender, EventArgs e)
{
//prevent running of duplicate tasks.
if(context.Session[SESSIONKEY]!=null && ((Task<int>)context.Session[SESSIONKEY]).IsCompleted==false) return;
var tcs = new TaskCompletionSource<int>();
Task.Run(async () =>
{
LogUtil.LogInfo("Getting data");
FillReportTable();
LogUtil.LogInfo("File upload is disabled");
string IOPath = Server.MapPath("~/Report-" + DateTime.Now.ToString("MM-dd-yyyy-hh-mm-ss") + ".xlsx");
if (System.IO.File.Exists(IOPath))
{
System.IO.File.Delete(IOPath);
}
System.IO.File.Copy(Server.MapPath("~/TempReport.xlsx"), IOPath);
ExportDSToExcel(IOPath);
if (Convert.ToBoolean(ConfigurationManager.AppSettings["ftpUpload"].ToString()))
{
UploadToFTP(IOPath);
}
else
{
LogUtil.LogInfo("File upload is disabled");
}
tcs.SetResult(1);
});
context.Session[SESSIONKEY] = tcs.Task;
lblMessage.Text = "File uploaded started";
}
然后在您的Web表单的HTML中添加使用ajax监视进度的方法:
<script>
$(document).ready(function() {
$('#btnSubmit").click(function() {checkStatus();});
}
function checkStatus() {
$.ajax
({
type: "GET",
url: "LongRunningTask.ashx?m=progress",
dataType: 'json',
success: function (data) {
$('#lblMessage').html(new Date() + ' ' + data);
if (data !== "Task Completed") {
window.setTimeout(checkStatus, 1000);
}
}
});
}
</script>
修改2
您新添加的代码还解释了数据保留在内存中的原因。您使用的数据存储为本地变量。当提交按钮单击方法中的代码正在执行时,对页面及其变量的引用仍保留在应用程序的内存中。即使浏览器断开连接,代码仍继续在服务器端执行。在完成执行并且页面生命周期完成之前,它不会从内存中释放。垃圾收集不会删除它,因为它仍然被引用。
您首先使用如此多内存的全部原因是您要将数据导出到Excel。根据我的经验,我看到excel的内存量是原始数据集的几倍。实际上,对于与您一样大的数据集,您将接近内存不足异常。如果您可以使用其他选项(例如CSV),则代码将以更快的速度运行,而不是几秒钟。
也就是说,一旦你的页面超出范围,它将不再被使用,并且内存将在垃圾收集时被释放。通过对所有操作使用完全不同的类(包括数据检索,转换和上载),可以加快这种速度。如果在单独的线程中实例化此任务,则一旦任务完成该类,其所有变量将超出范围并释放内存。