希望提高我的WebService异步调用的速度,但不确定如何

本文关键字:速度 不确定 调用 异步 高我的 我的 WebService 希望 | 更新日期: 2023-09-27 18:09:26

我正在编写一个使用第三方web服务并极大扩展其功能的web服务。例如,在工作流的一部分中,我必须循环遍历一个API调用的结果,并对每个结果进行另一个API调用,以便返回实际可用的结果。目前,这导致了大约7500行XML,以及3-4分钟的加载时间(当然,这个加载时间是基于在一台糟糕的PC上运行WebService的调试模式,在一台糟糕的互联网连接上,我希望它在一台高端的Windows服务器上运行时能更快一些)。我想做的是找到一种方法,为每个API调用生成一个新的异步线程(这样每次迭代就不必等待前一次迭代完成),但我不确定如何做到这一点,并且仍然能够在相同的函数调用中返回XML输出。什么好主意吗?

::EDIT::——这是我生成XML的代码。请注意,所有函数调用都只是对第三方API的API调用的包装。

public List<AvailabilityList> getResortsForDate(String month, int year) {
        List<RegionList> regions = this.getRegionLists( );
        List<AvailabilityList> availability = new List<AvailabilityList>();
        foreach(RegionList parent in regions)
        {
            foreach(Region child in parent.Regions)
            {
                if (!String.Equals(child.ID, "?"))
                {
                    int countryID = Int32.Parse(parent.CountryID);
                    AvailabilityList current = this.getExchangeAvailability(countryID, month, year, child.ID);
                    if (current.ListCount != 0)
                    {
                        availability.Add(current);
                    }
                }
            }
        }
        return availability;
    }

::EDIT #2:: SOLUTION!这是我最终使用的解决方案,这是对我选择的答案的一个小小的调整。谢谢!在计时我之前的代码(5分1秒)之后,这段代码是1分6秒的巨大改进,30秒的时间属于另一种方法,我也将优化!

public List<AvailabilityList> _asyncGetResortsForDate(String month, int year) {
        List<RegionList> regions = this.getRegionLists();
        List<AvailabilityList> availability = new List<AvailabilityList>();
        List<WaitHandle> handles = new List<WaitHandle>();
        List<AvailabilityList> _asyncResults = new List<AvailabilityList>();
        regions.ForEach(parent => {
            parent.Regions.ForEach(child => {
                    if (!String.Equals(child.ID, "?")) {
                        int countryID = Int32.Parse(parent.CountryID);
                        Func<AvailabilityList> _getList = () => this.getExchangeAvailability(countryID, month, year, child.ID);
                        IAsyncResult res = _getList.BeginInvoke(new AsyncCallback(
                            x => {
                                AvailabilityList result = (x.AsyncState as Func<AvailabilityList>).EndInvoke(x);
                                if (result.ListCount > 0)
                                {
                                    _asyncResults.Add(result);
                                }
                            }), _getList);
                        while (handles.Count >= 60)
                        {
                            int item = WaitHandle.WaitAny(handles.ToArray(  ));
                            handles.RemoveAt(item);
                        }
                        handles.Add(res.AsyncWaitHandle);
                    }
                });
        });
        WaitHandle.WaitAll(handles.ToArray());
        return _asyncResults;
    }

希望提高我的WebService异步调用的速度,但不确定如何

像这样乱搞等待句柄数组表明代码中有些东西太复杂了。你可以使用任务并行库来完成更简洁的工作。

例如:

public List<AvailabilityList> _asyncGetResortsForDate(String month, int year)
{
    List<RegionList> regions = this.getRegionLists();
    List<AvailabilityList> availability = new List<AvailabilityList>();
    List<Task> tasks = new List<Task>(); 
    List<AvailabilityList> _asyncResults = new List<AvailabilityList>(); 
    regions.ForEach(parent =>
    {
        parent.Regions.ForEach(child =>
        {
             if (!String.Equals(child.ID, "?"))
             {
                 int countryID = Int32.Parse(parent.CountryID);
                 var childId = child.ID;
                 Task t = Task.Factory.StartNew((s) =>
                     {
                         var rslt = getExchangeAvailability(countryId, month, year, childId);
                         lock (_asyncResults)
                         {
                             _asyncResults.Add(rslt);
                         }
                      });
                 tasks.Add(t);
             }
        }); 
    });
    Task.WaitAll(tasks);
    return _asyncResults; 
}

(我还没有尝试编译它,但是您可以了解其中的要点)

让TPL考虑64个等待句柄的限制。

还要注意,您的代码有一个等待发生的错误。由于多个任务可能试图将结果添加到_asyncResults列表,因此必须使用锁来保护它。List<T>.Add不是线程安全的。如果两个线程试图同时访问它,您将以数据损坏或异常告终。

上面的

也可能更快。我不确定如果启动多个异步调用会发生什么。线程池很可能会为它们创建最大数量的线程,并让它们全部运行。最终可能会有25个或更多正在运行的线程,并伴有上下文切换等。另一方面,TPL在使用线程方面要聪明得多。它将创建更少的并发线程,从而避免大量的上下文切换。

如果您使用Task<List<AvailabilityList>>,则可以完全避免锁定。你的代码就变成了这样:

Task<List<AvailabilityList>> t = Task<List<AvailabilityList>>.Factory.StartNew((s) =>
    {
        return getExchangeAvailability(countryId, month, year, childId);
    }

然后,在你的Task.WaitAll(tasks)之后:

foreach (var t in tasks)
{
    _asyncResults.Add(t.Result);
}

实际上,您可以去掉Task.WaitAll(tasks),因为Task<T>.Result会阻塞,直到获得结果。

这里有一种异步执行的方法,每次调用getExchangeAvailability()时,它都在一个单独的线程上执行,然后等待所有线程完成后返回最终列表。

    public List<AvailabilityList> _asyncGetResortsForDate(String month, int year)
    {
        List<RegionList> regions = this.getRegionLists();
        List<AvailabilityList> availability = new List<AvailabilityList>();
        List<WaitHandle> handles = new List<WaitHandle>(); 
        List<AvailabilityList> _asyncResults = new List<AvailabilityList>(); 
        regions.ForEach(parent =>
        {
            parent.Regions.ForEach(child =>
            {
                 if (!String.Equals(child.ID, "?"))
                 {
                     int countryID = Int32.Parse(parent.CountryID);
                     Func<AvailabilityList> _getList = () => 
                       this.getExchangeAvailability(countryID, month, year, child.ID);
                     IAsyncResult res = _getList.BeginInvoke(new AsyncCallback(
                       x =>
                       {
                          AvailabilityList result = 
                            (x.AsyncState as Func<AvailabilityList>).EndInvoke(x);
                          _asyncResults.Add(result);
                       }), _getList);
                     handles.Add(res.AsyncWaitHandle); 
                 }
            }); 
        });
        WaitHandle.WaitAll(handles.ToArray());
        return _asyncResults; 
    }

请记住,如果迭代次数超过64,因为并发线程的默认最大数量(使用BeginInvoke())是64,那么在此之后您将无法异步处理任何事情,直到64个已经运行的线程中的一个空闲为止。在线程之间进行上下文切换也可能有或没有一些开销。您可能想要检查的一件事是每个API调用本身需要多长时间,以查看它是否真的值得。

EDIT -关于64线程限制错误,我建议两件事,

1)你应该对异步调用进行分组,这样只有每个"父"线程在自己的线程上执行,而不是每个子线程。这样可以减少线程的数量,比如:

    public List<AvailabilityList> _getAllChildren(RegionList parent, string month, int year)
    {
        List<AvailabilityList> list = new List<AvailabilityList>(); 
        parent.Regions.ForEach(child =>
        {
            if (!String.Equals(child.ID, "?"))
            {
                int countryID = Int32.Parse(parent.CountryID);
                AvailabilityList result = this.getExchangeAvailability(countryID, month, year, child.ID);
                list.Add(result); 
            }
        });
        return list; 
    }
    public List<AvailabilityList> _asyncGetResortsForDate(String month, int year)
    {
        List<RegionList> regions = this.getRegionLists();
        List<AvailabilityList> availability = new List<AvailabilityList>();
        List<WaitHandle> handles = new List<WaitHandle>();
        List<AvailabilityList> _asyncResults = new List<AvailabilityList>(); 
        regions.ForEach(parent =>
        {
            Func<List<AvailabilityList>> allChildren = () => _getAllChildren(parent, month, year);
            IAsyncResult res = allChildren.BeginInvoke(new AsyncCallback(
                      x =>
                      {
                          List<AvailabilityList> result =
                            (x.AsyncState as Func<List<AvailabilityList>>).EndInvoke(x);
                          _asyncResults.AddRange(result);
                      }), allChildren);
            handles.Add(res.AsyncWaitHandle); 
        });
        WaitHandle.WaitAll(handles.ToArray());
        return _asyncResults; 
    }

2)你可能需要在添加到WaitHandles列表之前添加一个检查,看看你是否有超过64个线程的风险:

     var asyncHandle = res.AsyncWaitHandle; 
     if (handles.Count >= 64)
           asyncHandle.WaitOne();  // wait for this one now
     else if (handles.Count < 64)
           handles.Add(asyncHandle);