我从事一个ASP.NET MVC 4
项目,用户可以在UI上应用过滤器并提取Excel报告。数据存储在MS SQL server
中。为了提高性能,我决定在应用程序中采用async
:
为此,我的方法是:
当用户单击UI上的提取按钮->客户端将调用ajax
到服务器上的async
函数->此async
函数将依次创建一个Command
对象并执行ExecuteReaderAsync()
。使用此DbDatareader
通过NPOI
生成Excel文件,并将文件内容保存到TempData
。检索文件的处理程序将返回客户端,以供以后使用window.location
下载。我从这篇帖子Download Excel file via AJAX MVC
第一次提取后,如果用户要并行提取另一个数据集,则可以再次单击提取按钮,应用程序将重复步骤1。
结果是可以同时进行2次或更多次数据提取。
我的问题是,例如,当前并行运行的4个提取,如果其中任何一个提取完成并下载了1个文件(使用window.location
)。下次用户单击提取按钮(重复步骤1)时,它将不再同步,以后的提取将等待上一次提取完成后再执行。
在调试时,如果我重新启动ISS服务器,该问题会持续一会儿,直到下载1个文件为止,因此我怀疑window.location
所做的事情会在下载任何文件时阻塞服务器上的线程。
更新1
班级:
public class QUERYREADER
{
public DbConnection CONNECTION { get; set; }
public DbDataReader READER { get; set; }
}
型号:
public async Task<QUERYREADER> GET_DATA(CancellationToken ct)
{
//Create the query reader
QUERYREADER qr = new QUERYREADER();
//Set up the database instances
DbProviderFactory dbFactory = DbProviderFactories.GetFactory(db.Database.Connection);
//Defined the query
var query = "SELECT * FROM Table";
//Set up the sql command object
using (var cmd = dbFactory.CreateCommand())
{
//Try to open the database connection
try
{
//Check if SQL connection is set up
if (cmd.Connection == null)
{
cmd.CommandType = CommandType.Text;
cmd.Connection = db.Database.Connection;
}
//Open connection to SQL if current state is closed
if (cmd.Connection.State == ConnectionState.Closed)
{
//Change the connection string to set the packet size to max. value = 32768 to improve efficiency for I/O transmit to SQL server
cmd.Connection.ConnectionString = cmd.Connection.ConnectionString + ";Packet Size=20000";
//Open connection
await cmd.Connection.OpenAsync(ct);
}
//Save the connection
qr.CONNECTION = cmd.Connection;
} catch (Exception ex) {
//If errors throw, close the connection
cmd.Connection.Close();
};
//Retrieve the database reader of provided sql query
cmd.CommandText = query;
DbDataReader dr = await cmd.ExecuteReaderAsync(ct);
qr.READER.Add(dr);
}
//Return the queryreader
return qr;
}
控制器:
public async Task<JsonResult> SQL_TO_EXCEL()
{
//Set up the subscription to client for "cancellation request, browser closing"
CancellationToken disToken = Response.ClientDisconnectedToken;
//Get the datareader
try
{
qr = await GET_DATA(disToken);
}
catch(Exception ex) { }
//Open the connection to SQL server
using (qr.CONNECTION)
{
using (var dr = qr.READER)
{
while (await dr.ReadAsync(disToken))
{
for (int k = 0; k < dr.FieldCount; k++)
{
//.... using NPOI to write Excel file to MemoryStream
}
}
dr.Close();
}
}
//Generate XL file if controller action is still running (no "cancellation request, browser closing")
if (!disToken.IsCancellationRequested)
{
string file_id = Guid.NewGuid().ToString();
//... Write the NPOI excel file to TempData and then create a handler for later download at client
//This line caused trouble
TempData["file_id"] = XLMemoryStream.ToArray();
HANDLER["file_id"] = file_id;
HANDLER["file_name"] = FILE["FILE_NAME"].ToString().NonUnicode() + FILE["FILE_TYPE"].ToString() ;
}
//Return JSON to caller
var JSONRESULT = Json(JsonConvert.SerializeObject(HANDLER), JsonRequestBehavior.AllowGet);
JSONRESULT.MaxJsonLength = int.MaxValue;
return JSONRESULT;
}
public async Task<ActionResult> DOWNLOAD_EXCEL(string file_id, string file_name)
{
if (TempData[file_id] != null)
{
byte[] data = await Task.Run(() => TempData[file_id] as byte[]);
return File(data, "application/vnd.ms-excel", file_name);
}
else
{
return new EmptyResult();
}
}
JavaScript
$.ajax({
type: 'POST',
async: true,
cache: false,
url: 'SQL_TO_EXCEL',
success: function (data)
{
var response = JSON.parse(data);
window.location =
(
"DOWNLOAD_EXCEL" +
'?file_id=' + response.file_id +
'&file_name=' + response.file_name
);
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
console.log(errorThrown);
}
});
更新2:
经过大量测试,我发现window.location
与服务器上的线程无关,行TempData[file_id] = XLMemoryStream.ToArray()
引起了问题。看来问题与本帖子Two parallel ajax requests to Action methods are queued, why?