希望提高我的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;
}
像这样乱搞等待句柄数组表明代码中有些东西太复杂了。你可以使用任务并行库来完成更简洁的工作。
例如: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);