我正在编写一个使用第三方Web服务的WebService,并极大地扩展了功能。例如,在工作流的一部分中,我必须遍历一个API调用的结果,并且对于每个结果进行另一个API调用以返回实际可用的结果。目前,这导致大约7,500行XML,以及3-4分钟的加载时间(当然,这个加载时间是基于在具有糟糕的互联网连接的蹩脚PC上从Visual Studio运行调试模式的WebService,而我从高端Windows服务器运行时,预计它会变得更加快捷。我想要做的是找到一些方法为每个API调用生成一个新的Asyncronous线程(这样每次迭代都不必等待前一次迭代完成),但我不知道如何做到这一点仍然能够在同一个函数调用中返回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;
}
::编辑#2 ::解决方案! 这是我最终使用的解决方案,这是对我选择的答案的一个小调整。谢谢!在计算了我之前的代码(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;
}
答案 0 :(得分:1)
使用像这样的等待句柄数组来表示你的代码中有一些完全过于复杂的东西。您可以使用任务并行库完成更清晰的工作。
例如:
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
阻止,直到结果可用。
答案 1 :(得分:0)
以下是异步执行此操作的一种方法,每次调用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调用自己花多长时间来查看它是否真的值得。
编辑 - 关于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);