我们不得不通过无法控制的方式从提供给我们的有限的Web API中调用数据。逻辑流程如下:
For each product represented by a list of ID values
Get each batch of sub-categories of type FOO (100 records max. per call)
Keep calling the above until no records remain
Get each batch of sub-categories of type BAR (100 records max. per call)
Keep calling the above until no records remain
目前这已经产生了近100个Web API调用(我们已经询问了提供商,并且没有改进Web API来缓解这个问题。)
我担心由于这个原因,性能会受到严重影响,因此我试图了解异步替代方案,希望这会有所帮助。一个主要问题是数据只能被调用一次。之后,它会被锁定并且不会被重新发送,这是我们测试的一个主要限制。
我已阅读here,here和here,但我正在努力适应我的代码,因为我认为我需要两个等待来电而不是一个,我很担心搞砸了。
任何人都可以将await和async逻辑应用到这个伪代码中,以便我可以读取并尝试按照正在发生的事情流程进行操作吗?
public class DefaultController : Controller
{
public ActionResult Index()
{
var idlist = new List<String>() {"123", "massive list of strings....", "789"};
var xdoc = new XDocument();
xdoc.Declaration = new XDeclaration("1.0", Encoding.Unicode.WebName, "yes");
var xroot = new XElement("records");
xdoc.Add(xroot);
foreach (string id in idlist)
{
// Get types FOO -----------------------------------
Boolean keepGoingFOO = true;
while (keepGoingFOO)
{
// 100 records max per call
var w = new WebServiceClient();
request.enumType = enumType.FOO;
var response = w.response();
foreach (ResultItem cr in response.ResultList)
{
var xe = new XElement("r");
// create XML
xroot.Add(xe);
}
keepGoingFOO = response.moreRecordsExist;
}
// Get types BAR -----------------------------------
Boolean keepGoingBAR = true;
while (keepGoingBAR)
{
// 100 records max per call
var w = new WebServiceClient();
request.enumType = enumType.BAR;
var response = w.response();
foreach (ResultItem cr in response.ResultList)
{
var xe = new XElement("r");
// create XML
xroot.Add(xe);
}
keepGoingBAR = response.moreRecordsExist;
}
}
return View(xdoc);
}
}
答案 0 :(得分:1)
应该让你开始:
public async ActionResult Index()
{
var idlist = new List<string>() { "123", "massive list of strings....", "789" };
IEnumerable<XElement> list = await ProcessList(idlist);
//sort the list as it will be completely out of order
return View(xdoc);
}
public async Task<IEnumerable<XElement>> ProcessList(IEnumerable<string> idlist)
{
IEnumerable<XElement>[] processList = await Task.WhenAll(idlist.Select(FooBar));
return processList.Select(x => x.ToList()).SelectMany(x => x);
}
private async Task<IEnumerable<XElement>> FooBar(string id)
{
Task<IEnumerable<XElement>> foo = Foo(id);
Task<IEnumerable<XElement>> bar = Bar(id);
return ((await bar).Concat(await foo));
}
private async Task<IEnumerable<XElement>> Bar(string id)
{
var localListOfElements = new List<XElement>();
var keepGoingFoo = true;
while (keepGoingFoo)
{
var response = await ServiceCallAsync(); //make sure you use the async version
localListOfElements.Add(new XElement("r"));
keepGoingFoo = response.moreRecordsExist;
}
return localListOfElements;
}
private async Task<IEnumerable<XElement>> Foo(string id)
{
var localListOfElements = new List<XElement>();
var keepGoingFoo = true;
while (keepGoingFoo)
{
var response = await ServiceCallAsync(); //make sure you use the async version
localListOfElements.Add(new XElement("r"));
keepGoingFoo = response.moreRecordsExist;
}
return localListOfElements;
}
private async Task<Response> ServiceCallAsync()
{
await Task.Delay(1000);//simulation
return new Response();
}
答案 1 :(得分:1)
您的代码存在多个问题,通过重构可以大大改善,因为for循环之间唯一更改的项是request.EnumType。通过正确使用异步等待可以大大提高性能 - 只要id是独立的,问题就不是并行化两个 - 而是尽可能地并行化。
减慢时间的部分不是xml访问 - 它是web api调用。
我会将其重构为
async Task<Tuple<string, enumType, XElement>> SendRequest(string id, enumType input){
..
}
用
替换for循环List<Tuple<string, enumType>> tupleList = idList.Select(id => Tuple.Create(id, enumType.BAR)).ToList();
tupleList.Concat(idList.Select(id => Tuple.Create(id, enumType.FOO)).ToList());
Task<Tuple<string, enumType, XElement>>[] all = tupleList
.Select(c => SendRequest(c.Item1, c.Item2))
.ToArray();
var res = await Task.WhenAll(tasks);
res变量将包含您要添加的所有要快速的XElement值。你可以使用一个Key Value对,id-enumType的元组是关键,但想法是一样的。
答案 2 :(得分:1)
为了使解决方案更加优雅,我会隐藏在可枚举对象后面的批处理,例如 weston ,但不是将所有项目放在一个列表中 - 只要它们可用就消耗它们(最小化内存利用率。)
使用AsyncEnumerator NuGet Package,您可以编写如下代码:
public class DefaultController : Controller
{
public async ActionResult Index()
{
var idlist = new List<String>() { "123", "massive list of strings....", "789" };
var xdoc = new XDocument();
xdoc.Declaration = new XDeclaration("1.0", Encoding.Unicode.WebName, "yes");
var xroot = new XElement("records");
xdoc.Add(xroot);
foreach (string id in idlist) {
// Get types FOO -----------------------------------
var foos = EnumerateItems(enumType.FOO);
await foos.ForEachAsync(cr => {
var xe = new XElement("r");
// create XML
xroot.Add(xe);
});
// Get types BAR -----------------------------------
var bars = EnumerateItems(enumType.BAR);
await foos.ForEachAsync(cr => {
var xe = new XElement("r");
// create XML
xroot.Add(xe);
});
}
return View(xdoc);
}
public IAsyncEnumerable<ResultItem> EnumerateItems(enumType itemType)
{
return new AsyncEnumerable<ResultItem>(async yield => {
Boolean keepGoing = true;
while (keepGoing) {
// 100 records max per call
var w = new WebServiceClient();
request.enumType = itemType;
// MUST BE ASYNC CALL
var response = await w.responseAsync();
foreach (ResultItem cr in response.ResultList)
await yield.ReturnAsync(cr);
keepGoing = response.moreRecordsExist;
}
});
}
}
注1:通过使所有async
实际上不会提高单个例程的性能,实际上使其稍慢(由于异步状态机和TPL任务的开销)。但是,它有助于更好地利用整个应用程序中的工作线程。
注意2:您的客户端请求必须是Async,否则优化没有意义 - 如果您同步执行它会阻塞线程并在服务器响应时等待。
注3:请将CancellationToken传递给每个Async方法 - 这是一个很好的做法。
注意4:根据代码判断,您有一个Web服务器,因此我不建议并行运行所有项目的所有批处理并执行Task.WhenAll
等待它们 - 如果您的服务客户端是同步的( w.response
调用)然后它会阻止很多线程,你的整个网络服务可能会没有响应。
在建议的解决方案中,你可以做一个并行处理的技巧 - 你可以在下一批处理前读取当前的那个:
public IAsyncEnumerable<ResultItem> EnumerateItemsWithReadAhead(enumType itemType)
{
return new AsyncEnumerable<ResultItem>(async yield => {
Task<Response> nextBatchTask = FetchNextBatch(itemType);
Boolean keepGoing = true;
while (keepGoing) {
var response = await nextBatchTask;
// Kick off the next batch request (read ahead)
keepGoing = response.moreRecordsExist;
if (keepGoing)
nextBatchTask = FetchNextBatch(itemType);
foreach (ResultItem cr in response.ResultList)
await yield.ReturnAsync(cr);
}
});
}
private Task<Response> FetchNextBatch(enumType itemType)
{
// 100 records max per call
var w = new WebServiceClient();
request.enumType = itemType;
// MUST BE ASYNC
return w.responseAsync();
}