2017-06-30 14 views
-2

ボタンが付いたページがあります。クリックすると、テーブルから539,200行を取り出して、OpenXML SDKでExcelファイルを作成しています。 WebClient.UploadFileメソッドを使用してFTPにアップロードする50MB以上のファイルを作成します。このプロセス全体では、IISワーカープロセスのCPU使用率は30%になり、メモリ使用率は1.2GBになります。全プロセスは私のサーバーで完了するのに約10分かかります。完了後、ブラウザ(Firefox)でさらに10分かかると、「接続がリセットされました」というエラーが表示されます。以前の実行完了前の10分後に、ログで別の実行が開始されたことがわかりました。私は誰もそのサーバーを使用していないと確信しています。C# - ブラウザが重複要求を送信していて、長時間実行されているプロセスでエラー接続がリセットされました。

私の質問

  1. プロセスが完了した後にメモリ使用率がダウンして来なかったのはなぜ?私は慎重にそれぞれのオブジェクトごとに&を配置しました。ガベージコレクタと呼ばえさえ。メモリを解放するためにIISを再起動する必要がありました。コードで何ができますか?

  2. 私のログごとに、合計実行は10分で終了しましたが、ブラウザは応答せず、「接続中.....」と表示され続けました。さらに10分後に「接続がリセットされました」というエラーが表示されました。何が悪かったのか?

  3. 前回終了前に別の実行ログが表示されたのはなぜですか?ブラウザが別のリクエストを送信していますか、IIS/ASP.Netが狂っていますか?

  4. 実行時にAjaxリクエストを使用すると、IISを再起動するまで10分ごとにログエントリが繰り返されていました。何が起こった?

繰り返しログエントリは、同じプロセスが繰り返し実行されることを意味します。今私は怒っている。

私はデータ

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; 
     } 
    } 

を取得するには、編集 IIS 8

をのWindows Server 2012を使用しています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"); 
    } 

ボタンのClickイベントのコード

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

この現象が発生する可能性がある理由はたくさんありますが、一般的に言えば、ブラウザが応答するまで10分間待機する必要があります。代わりに、一般的に言えば、その前にブラウザに応答を返し、定期的にアプリケーションをポーリングしてタスクが完了したかどうかを確認する必要があります。もちろん、WebSocketを使用してプロセスを開始し、応答を待つなどの他の方法もあります。

  1. アプリケーションに依然としてオブジェクトへの参照がある場合、オブジェクトがガベージコレクションされてもメモリに残ります。あなたのコードを見ずにこの参照がどこにあるのかを言うのは難しいです。

  2. 説明したように、ブラウザは応答を待つのをやめて、10分が経過するのを待ってから元の接続を閉じます。

  3. ブラウザは、応答しない接続をクローズした後、リクエストを自動的に試みた可能性があります。

  4. あなたのコードを見ずには分かりません。

長時間実行されるタスクを実行するための簡単な方法は、あなたが行っている述べたように、アヤックスを使用して、それをトリガーとSystem.Threading.Tasks.Taskを使用してタスクを実行しています。必要に応じてタスクへの参照を保存することができます。 Ajaxを使用して、タスクのステータスをポーリングして、完了したかどうかを確認できます。

サーバー側の汎用ハンドラを使用して完了するタスクを実行するボイラープレートの実装と、Ajaxを使用してタスクを開始して進行状況を監視するためにJQueryを使用する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> 

編集

新しく追加されたコードを見て、あなたは同じアプローチを統合することができます。

プロジェクトに汎用ハンドラを追加します。タスクランナーとスイッチを「開始」として削除することができます。

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を使用して進行状況を監視する方法を追加します:タスクの内部でコードを実行するには

修正btnSubmit_Click

<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にエクスポートすることです。私の経験では、元のデータセットの数倍のメモリ量を凌駕しています。実際、あなたのものと同じ大きさのデータセットでは、メモリ不足の例外に近づいています。 CSVなどの別のオプションを使用できる場合、コードでは数分ではなく数秒で命令が実行されます。

あなたのページがスコープの外に出るとメモリは使用されなくなり、メモリはガベージコレクションの際に解放されます。データの取得、変換、アップロードなど、すべての操作に全く異なるクラスを使用することで、これをより速く実行させることができます。このタスクを別のスレッドでインスタンス化すると、タスクがクラスを完了すると、その変数のすべてが有効範囲外になり、メモリが解放されます。

+0

メモリの問題を解決する方法を提案してください。私は狂っていると感じています。過去16時間からこれに取り組んでいます。 –

+0

答えの最後に新しいコメントが追加されました。一言で言えば、これを行うことができれば、すべてのコストでExcelを使用しないようにしてください。代わりにCSVのようなことをしてみてください。いずれの場合も、すべての操作を別のクラスに移動し、タスク内でそのクラスをインスタンス化します。タスクが完了すると、クラスはスコープから外れて、ガベージコレクション時に解放されます。完了後、必要に応じてすべてを手動でクリーンアップすることもできます。 –

+0

ブラウザやブラウザからの複数のリクエストが応答なしで複数のリクエストを送信しているような、繰り返しの実行の理由は何ですか?ちょうど私の知識のために。 –

関連する問題