C# - 浏览器正在发送重复请求,并且为长时间运行的进程重置了错误连接

时间:2017-06-30 21:14:38

标签: c# asp.net .net iis openxml-sdk

我有一个带按钮的页面。点击后,我从表中获取539,200行,并使用OpenXML SDK创建excel文件。它创建了超过50MB的文件,我通过WebClient.UploadFile方法在FTP上传。在整个过程中,IIS Worker进程CPU消耗率达到30%,内存利用率达到1.2 GB。整个过程大约需要10分钟才能在我的服务器上完成。完成后,浏览器(Firefox)需要额外的10分钟,并显示错误"连接已重置"。在上一次执行完成之前10分钟后,我看到在日志中开始了另一次执行。我确信没有其他人在使用该服务器。

我的问题

  1. 为什么在流程完成后内存利用率没有下降?我仔细处理了每一个&每个对象。甚至称为垃圾收集器。我不得不重新启动IIS以释放内存。我可以在代码中做什么?

  2. 根据我的日志,总执行时间在10分钟内完成,但浏览器仍然没有响应,它一直显示"正在连接......"。又过了大约10分钟,它出错了"连接被重置"。出了什么问题?

  3. 为什么我在上一次结束之前看到了另一个执行日志?是浏览器发送另一个请求还是IIS / ASP.Net疯了?

  4. 当使用Ajax请求执行时,我每隔~10分钟就会看到重复的日志条目,直到我重新启动IIS。发生了什么事?

  5. 重复的日志条目转换为重复执行相同的进程。我现在生气了。

    我正在使用带有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。

1 个答案:

答案 0 :(得分:1)

您可能遇到此行为的原因有很多,但一般来说,您需要跳过篮球才能让浏览器等待10分钟才能得到响应。相反,一般来说,您应该在此之前返回对浏览器的响应,并定期轮询您的应用程序以查看任务是否已完成。当然还有其他方法,比如使用websockets来启动进程并等待响应。

  1. 如果您的应用程序仍然具有对象的引用,它将保留在内存中,即使您已收集垃圾。如果没有看到您的代码,很难说这个引用可能在哪里。

  2. 如上所述,浏览器将在10分钟之前停止等待响应并关闭基础连接。

  3. 关闭非响应连接后,浏览器完全可能会自动尝试请求。

  4. 很难说没看到你的代码。

  5. 运行长时间运行任务的简单方法是使用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),则代码将以更快的速度运行,而不是几秒钟。

    也就是说,一旦你的页面超出范围,它将不再被使用,并且内存将在垃圾收集时被释放。通过对所有操作使用完全不同的类(包括数据检索,转换和上载),可以加快这种速度。如果在单独的线程中实例化此任务,则一旦任务完成该类,其所有变量将超出范围并释放内存。