应用async并等待冗长的while循环

时间:2016-03-18 14:33:50

标签: c# .net asp.net-mvc asynchronous async-await

我们不得不通过无法控制的方式从提供给我们的有限的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来缓解这个问题。)

我担心由于这个原因,性能会受到严重影响,因此我试图了解异步替代方案,希望这会有所帮助。一个主要问题是数据只能被调用一次。之后,它会被锁定并且不会被重新发送,这是我们测试的一个主要限制。

我已阅读hereherehere,但我正在努力适应我的代码,因为我认为我需要两个等待来电而不是一个,我很担心搞砸了。

任何人都可以将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);
    }
}

3 个答案:

答案 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();
    }