背景:
每年我们都会运行并归档来自特定 ASP.Net 应用程序的所有报告。该档案提供了系统数据在一年中特定时间的“快照”。我们使用 .Net Forms 构建了一个 GUI,它使用 System.Net.WebClient 调用报表服务器并将报表下载到特定目录。这在过去运作良好。
今年我们将 Excel 文件包含在我们的档案中。Excel 文件由 .aspx 页面(Windows Sever 2003、IIS6、.Net 4.0)创建,该页面接受查询字符串,然后返回 Excel 文件。这适用于 100 个左右的 Excel 文件,但之后我们开始遇到问题。我们每年归档大约 300,000 个文件。
力学:
我们使用 WebClient.DownloadFileAsync 来拉取我们的文件,以免锁定 UI 线程。我们依靠 WebClient.DownloadFileCompleted 事件来告诉我们每个文件何时完成下载。当提出 DownloadFileCompleted 时,我们开始下载下一个文件。
问题:
我们正在锁定网络服务器。每个文件只需要几分之一秒的时间来下载,大约 167 个文件后,网络服务器锁定(页面超时),存档过程暂停几分钟。然后存档过程又下载了大约 100 个文件,并再次停止了几分钟。这会持续几个小时,直到归档过程开始每分钟左右抓取一个文件。
看来 IIS6 的线程已用完,但我们如何才能阻止这种情况发生呢?
以下是我们正在运行的代码的精简版本。我已经删除了与我们的问题无关的日志记录和其他项目。有没有人有任何提示?
public class DownloadExample
{
private WebClient _WebClient = new WebClient();
public string DownloadDirectory { get; set; }
public List<Report> ReportList { get; set; }
/// <summary>
/// Constructor - sets all the attributes needed to access the report server, download, and archive the reports
/// </summary>
/// <param name="userName">Username</param>
/// <param name="userPassword">Password for the user's domain username</param>
/// <param name="userDomain">Domain of the username</param>
/// <param name="downloadDirectory">Network path where the files will be archived</param>
public DownloadExample(string userName, string userPassword, string userDomain, string downloadDirectory, List<Report> reportList)
{
DownloadDirectory = downloadDirectory;
_WebClient.Credentials = new NetworkCredential(userName, userPassword, userDomain);
_WebClient.DownloadFileCompleted += new System.ComponentModel.AsyncCompletedEventHandler(WebClient_DownloadFileCompleted);
ReportList = reportList;
}
/// <summary>
/// Kicks off the archive process
/// </summary>
public void StartDownloading()
{
if (ReportList.Count > 0)
{
Report rpt = ReportList[0];
DoDownload(rpt.URL, CreateFileName(rpt), rpt.ReportTitle, rpt.ReportFormatType);
}
}
/// <summary>
/// Run the report and then download it to the archive directory
/// </summary>
/// <param name="url">URL of the Report</param>
/// <param name="fileName">File name used to name the report file once it is downloaded</param>
/// <param name="folderName">Name of the folder where the report will be downloaded to</param>
/// <param name="reportFormatType">Type of report being run, PDF or Excel</param>
private bool DoDownload(string url, string fileName, string folderName, ReportFormatTypes reportFormatType)
{
bool isSuccess = false;
string folderPath = DownloadDirectory + "\\" + folderName;
DirectoryInfo dir = new DirectoryInfo(folderPath);
if (!dir.Exists)
{
dir.Create();
dir = null;
dir = new DirectoryInfo(folderPath);
}
if (dir.Exists)
{
string path = folderPath + "\\" + fileName + ".xls";
System.Uri uri = new Uri(url);
try
{
_WebClient.DownloadFileAsync(uri, path);
}
catch (Exception exp)
{
//log error
}
FileInfo file = new FileInfo(path);
isSuccess = file.Exists;
}
return isSuccess;
}
/// <summary>
/// This event is fired after a file is downloaded
/// After each file is downloaded, we remove the downloaded file from the list,
/// then download the next file.
/// </summary>
void WebClient_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
//Remove the report that was just run
ReportList.RemoveAt(0);
if (ReportList.Count > 0)
{
//Download the next report
Report rpt = ReportList[0];
DoDownload(rpt.URL, CreateFileName(rpt), rpt.ReportTitle, rpt.ReportFormatType);
}
}
/// <summary>
/// Does a bunch of stuff to create the file name...
/// </summary>
string CreateFileName(Report rpt)
{
return rpt.FileName;
}
}