【问题标题】:Asynchronous requests to API, using yield iterator?对 API 的异步请求,使用 yield 迭代器?
【发布时间】:2016-12-12 12:34:03
【问题描述】:

我在选择对 Web API 进行异步请求的最佳方式时遇到了一些麻烦。我需要从连续的网络请求中获取数据,满足以下要求:

  • 请求显然不能阻塞 GUI(异步或在另一个线程中)
  • 每个请求都会返回一个记录列表,我想立即将其与我的应用数据合并
  • 请求不能同时运行(保持“逻辑”顺序)
  • 如果请求失败,则必须取消剩余的请求

Web 请求不是 CPU 密集型的,只是速度很慢。此外,由于我想在每次请求后合并结果,使用不同的线程会导致一些同步开销。

这就是为什么我认为最好使用 async/await 来避免创建新线程。首先,这个假设正确吗?


我的(不完整的)解决方案:

我使用yield 提出了一个优雅的解决方案,但它没有实现预期的async 行为。

这是简化的 API 连接器代码:

public IEnumerable<Record[]> GetRecordsInRange(DateTime startDate, DateTime endDate)
{
    while (startDate <= endDate)
    {
        List<Record> results = new List<Record>();

        try
        {
            results.AddRange(ExecuteQueryAPI(startDate, QueryTypes.A));
            results.AddRange(ExecuteQueryAPI(startDate, QueryTypes.B));
            results.AddRange(ExecuteQueryAPI(startDate, QueryTypes.C));
        }
        catch (Exception ex)
        {
            // Cancel remaining requests
            yield break;
        }

        // Return partial results.
        yield return results.ToArray();

        // Jump to the next day.
        startDate = startDate.AddDays(1);
    }

    yield break;
}

用法:

foreach (var records in APIConnector.GetRecordsInRange(DateTime.Now.AddDays(-10), DateTime.Now))
{
    Records.AddRange(records);
}

是否可以对之前的代码实现 aync/await 行为,还是应该使用不同的方法?

【问题讨论】:

标签: c# asynchronous async-await yield-return


【解决方案1】:

async-await 在您的情况下是足够好的方法。
这种方法将只使用一个线程,不会阻塞 UI。

public async Task<IEnumerable<Record>> GetRecordsInRange(DateTime startDate, 
                                                         DateTime endDate)
{
    while (startDate <= endDate)
    {
        List<Record> results = new List<Record>();
        // send requests in order you want
        var taskA = ExecuteQueryAPIAsync(startDate, QueryTypes.A);
        var taskB = ExecuteQueryAPIAsync(startDate, QueryTypes.B);
        var taskC = ExecuteQueryAPIAsync(startDate, QueryTypes.C);

        // observes results in same order
        var resultA = await taskA;
        results.AddRange(resultA);

        var resultB = await taskB;
        results.AddRange(resultB);

        var resultC = await taskC;
        results.AddRange(resultC);

        // Jump to the next day.
        startDate = startDate.AddDays(1);
    }

    return results;
}

您需要创建ExecuteQueryAPIAsync 方法来异步获取响应。

await taskA 关键字将返回结果集合或抛出异常。

【讨论】:

    【解决方案2】:

    我在您的代码中看到的一个问题是,您为每个产量运行三个 api 请求,使每次迭代的速度是必须的三倍。我会先把它改写成这样:

        public IEnumerable<Record[]> GetRecordsInRange(DateTime startDate, DateTime endDate)
        {
            while(startDate <= endDate)
            {
                // This hard-coded stuff should be replaced with something prettier but that's off topic and comes down to your implementation and the meaning of QueryTypes
                // I choose to isolate it to the delegates variable to make it easy to rewrite and perhaps instantiate it from a function
                var delegates = new Func<Record[]>[] 
                {
                    ()=>ExecuteQueryAPI(startDate, QueryTypes.A),
                    ()=>ExecuteQueryAPI(startDate, QueryTypes.B),
                    ()=>ExecuteQueryAPI(startDate, QueryTypes.C)
                };
    
                foreach(var d in delegates)
                {
                    Record[] output;
                    try
                    {
                        output = d();
                    }
                    catch
                    {
                        yield break;
                    }
                    yield return output;
                }
    
                startDate = startDate.AddDays(1);
            }
        }
    

    这将使代码正常运行,并且异步运行应该相当简单

    【讨论】:

    • 你说得对,但这是设计使然!因为我的结果必须包括 A、B 和 C 类结果,否则什么都不包括 :)
    猜你喜欢
    • 2011-04-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-04
    • 1970-01-01
    • 2021-11-04
    相关资源
    最近更新 更多