同时限制 # 个 Web 服务请求

本文关键字:Web 服务 请求 | 更新日期: 2023-09-27 18:36:34

我有一个用C#.NET 4.5编写的Excel加载项。它将向 Web 服务器发送许多 Web 服务请求以获取数据。 例如,它向 Web 服务服务器发送 30,000 个请求。当请求的数据返回时,加载项将在Excel中绘制数据。最初我异步执行了所有请求,但有时我会得到内存不足异常所以我改变了,一个接一个地发送请求,但它太慢了,需要很长时间才能完成所有请求。我想知道是否有一种方法可以一次异步执行 100 个请求,一旦所有 100 个请求的数据返回并在 Excel 中绘制,然后发送接下来的 100 个请求。

谢谢

编辑
在我的插件上,有一个功能区按钮"刷新",单击它时,刷新过程开始。

在主 UI 线程上,单击功能区/按钮,它将调用 Web 服务 BuildMetaData,一旦它被返回,在其回调 MetaDataCompleteCallback 中,将发送
另一个 Web 服务调用返回后,在其回调 DataRequestJobDone 中,它将调用 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);
        }
    }  
}

在要生成的类报告中

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 = ... 
}

同时限制 # 个 Web 服务请求

OutOfMemoryException不是关于同时发送的太多请求。这是关于以正确的方式释放您的资源。在我的实践中,当您遇到此类异常时,有两个主要问题:

  • 使用不可变结构或System.String类时出错
  • 不释放一次性资源,尤其是图形对象和 WCF 请求。

在我看来,在报告的情况下,您遇到了第二种类型的问题。 DataRequestDataRequestResponse是开始调查此类对象的好点。

如果这没有帮助,请尝试使用具有async/await模式的Tasks库,您可以在此处找到很好的例子:

// 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循环,您可以在其中存储 100 大小的Task[],您可以使用 WaitAll 方法,应该解决问题。抱歉,但是您的代码足够大,我无法为您提供更直接的示例。

我在解析您的代码以弄清楚是否正在为您的请求迭代时遇到了很多麻烦,但异步批处理的基本模板将是这样的:

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 深的调用堆栈,所以你会没事的。

请注意,这也假设您的数据请求内容是异步完成的,并返回一个Task。大多数库都已更新为执行此操作(查找带有异步后缀的方法)。如果它没有公开该 api,您可能需要创建一个单独的问题,专门介绍如何让您的库与 TPL 很好地配合使用。