同时限制Web服务请求数

时间:2015-01-07 21:03:52

标签: c# .net web-services out-of-memory

我有一个用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 = ... 
}

2 个答案:

答案 0 :(得分:3)

OutOfMemoryException与同时发送的请求太多无关。它是以正确的方式释放您的资源。在我的实践中,当你遇到这样的例外时有两个主要问题:

  • 使用不可变结构或System.String
  • 时使用错误
  • 不处理您的可支配资源,尤其是图形对象和WCF请求。

如果是举报,我认为你有第二种类型的问题。 DataRequestDataRequestResponse是开始调查此类对象的好方法。

如果这样做无效,请尝试使用Tasksasync/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一起玩。

相关问题