我有一个用C#,.NET 4.5编写的Excel加载项。它会向Web服务器发送许多Web服务请求以获取数据。例如。它向Web服务服务器发送30,000个请求。当请求的数据返回时,插件将在Excel中绘制数据。 最初我是异步执行所有请求,但有时我会得到OutOfMemoryException 所以我改变了,逐个发送请求,但是太慢了,需要很长时间才能完成所有请求。 我想知道是否有一种方法可以异步地一次完成100个请求,一旦所有100个请求的数据都返回并在Excel中绘图,然后发送接下来的100个请求。
谢谢
编辑
在我的插件上,有一个功能区按钮"刷新"当点击它时,刷新过程开始。
在主UI线程上,单击功能区/按钮,它将调用Web服务BuildMetaData,
一旦它返回,在其回调MetaDataCompleteCallback中,发送另一个Web服务调用
一旦它返回,在其回调DataRequestJobFinished中,它将调用plot在Excel上绘制数据。见下文
RefreshBtn_Click()
{
if (cells == null) return;
Range firstOccurence = null;
firstOccurence = cells.Find(functionPattern, null,
null, null,
XlSearchOrder.xlByRows,
XlSearchDirection.xlNext,
null, null, null);
DataRequest request = null;
_reportObj = null;
Range currentOccurence = null;
while (!Helper.RefreshCancelled)
{
if(firstOccurence == null ||IsRangeEqual(firstOccurence, currentOccurence)) break;
found = true;
currentOccurence = cells.FindNext(currentOccurence ?? firstOccurence);
try
{
var excelFormulaCell = new ExcelFormulaCell(currentOccurence);
if (excelFormulaCell.HasValidFormulaCell)
{
request = new DataRequest(_unityContainer, XLApp, excelFormulaCell);
request.IsRefreshClicked = true;
request.Workbook = Workbook;
request.Worksheets = Worksheets;
_reportObj = new ReportBuilder(_unityContainer, XLApp, request, index, false);
_reportObj.ParseParameters();
_reportObj.GenerateReport();
//this is necessary b/c error message is wrapped in valid object DataResponse
//if (!string.IsNullOrEmpty(_reportObj.ErrorMessage)) //Clear previous error message
{
ErrorMessage = _reportObj.ErrorMessage;
Errors.Add(ErrorMessage);
AddCommentToCell(_reportObj);
Errors.Remove(ErrorMessage);
}
}
}
catch (Exception ex)
{
ErrorMessage = ex.Message;
Errors.Add(ErrorMessage);
_reportObj.ErrorMessage = ErrorMessage;
AddCommentToCell(_reportObj);
Errors.Remove(ErrorMessage);
Helper.LogError(ex);
}
}
}
在Class to GenerateReport
上public void GenerateReport()
{
Request.ParseFunction();
Request.MetacompleteCallBack = MetaDataCompleteCallback;
Request.BuildMetaData();
}
public void MetaDataCompleteCallback(int id)
{
try
{
if (Request.IsRequestCancelled)
{
Request.FormulaCell.Dispose();
return;
}
ErrorMessage = Request.ErrorMessage;
if (string.IsNullOrEmpty(Request.ErrorMessage))
{
_queryJob = new DataQueryJob(UnityContainer, Request.BuildQueryString(), DataRequestJobFinished, Request);
}
else
{
ModifyCommentOnFormulaCellPublishRefreshEvent();
}
}
catch (Exception ex)
{
ErrorMessage = ex.Message;
ModifyCommentOnFormulaCellPublishRefreshEvent();
}
finally
{
Request.MetacompleteCallBack = null;
}
}
public void DataRequestJobFinished(DataRequestResponse response)
{
Dispatcher.Invoke(new Action<DataRequestResponse>(DataRequestJobFinishedUI), response);
}
public void DataRequestJobFinished(DataRequestResponse response)
{
try
{
if (Request.IsRequestCancelled)
{
return;
}
if (response.status != Status.COMPLETE)
{
ErrorMessage = ManipulateStatusMsg(response);
}
else // COMPLETE
{
var tmpReq = Request as DataRequest;
if (tmpReq == null) return;
new VerticalTemplate(tmpReq, response).Plot();
}
}
catch (Exception e)
{
ErrorMessage = e.Message;
Helper.LogError(e);
}
finally
{
//if (token != null)
// this.UnityContainer.Resolve<IEventAggregator>().GetEvent<DataQueryJobComplete>().Unsubscribe(token);
ModifyCommentOnFormulaCellPublishRefreshEvent();
Request.FormulaCell.Dispose();
}
}
关于情节课
public void Plot()
{
...
attributeRange.Value2 = headerArray;
DataRange.Value2 = ....
DataRange.NumberFormat = ...
}
答案 0 :(得分:3)
OutOfMemoryException
与同时发送的请求太多无关。它是以正确的方式释放您的资源。在我的实践中,当你遇到这样的例外时有两个主要问题:
System.String
类如果是举报,我认为你有第二种类型的问题。 DataRequest
和DataRequestResponse
是开始调查此类对象的好方法。
如果这样做无效,请尝试使用Tasks
库async/await
模式,您可以找到好的示例here:
// Signature specifies Task<TResult>
async Task<int> TaskOfTResult_MethodAsync()
{
int hours;
// . . .
// Return statement specifies an integer result.
return hours;
}
// Calls to TaskOfTResult_MethodAsync
Task<int> returnedTaskTResult = TaskOfTResult_MethodAsync();
int intResult = await returnedTaskTResult;
// or, in a single statement
int intResult = await TaskOfTResult_MethodAsync();
// Signature specifies Task
async Task Task_MethodAsync()
{
// . . .
// The method has no return statement.
}
// Calls to Task_MethodAsync
Task returnedTask = Task_MethodAsync();
await returnedTask;
// or, in a single statement
await Task_MethodAsync();
在您的代码中,我看到了一个while
循环,您可以在其中存储Task[]
大小100
,您可以使用WaitAll
method和{{1}}问题应该解决。对不起,但你的代码非常庞大,我无法为你提供更直接的例子。
答案 1 :(得分:1)
我在解析你的代码时很难解决你的请求,但是异步批处理的基本模板将是这样的:
static const int batchSize = 100;
public async Task<IEnumerable<Results>> GetDataInBatches(IEnumerable<RequestParameters> parameters) {
if(!parameters.Any())
return Enumerable.Empty<Result>();
var batchResults = await Task.WhenAll(parameters.Take(batchSize).Select(doQuery));
return batchResults.Concat(await GetDataInBatches(parameters.Skip(batchSize));
}
其中doQuery
是签名
Task<Results> async doQuery(RequestParameters parameters) {
//.. however you do the query
}
自递归以来,我不会将这个用于一百万个请求,但你的情况应该会产生一个只有300深度的callstack,这样你就可以了。
请注意,这也假设您的数据请求内容是异步完成的,并返回Task
。大多数库已更新为执行此操作(查找具有Async后缀的方法)。如果它没有公开该api,你可能想要创建一个单独的问题,以便如何专门让你的库与TPL一起玩。