【问题标题】:Any benefit to an Async method that Synchronously Loops a call同步循环调用的异步方法的任何好处
【发布时间】:2018-11-09 16:39:51
【问题描述】:

我正在编写一个必须通过 SDK 从外部 api 提取数据的程序。我不精通等待/异步操作 - 但知道每个调用的 SDK 都提供了一个异步方法。 GetResultsAsync<TObject>()

每个查询都有返回记录的限制 - 所以为了获得当月的所有记录 - 我必须按 ID 排序,然后循环。这是当前同步调用的一些示例代码。只要我得到结果 - 我会检查更多。

public List<TimeEntry> ListTimeEntries(DateTime startDate, DateTime endDate)
{
    var page = 1;
    var hasMore = false;
    var qry = string.Format("timeStart >= [{0}] and timeEnd <= [{1}]", startDate, endDate); //For example
    var results = List<TimeEntry>();
    do
    {
        var newItems = api.GetEntries(qry, "id desc", maxPageSize, page).GetResults<List<TimeEntry>>();
        results.AddRange(newItems);
        hasMore = newItems.Any();
        page++;
    } while (hasMore);
    return results;
}

这可能构成 4-5 个循环(或更多,具体取决于日期),但显然很耗时。

如果我简单地将其转换为 async 方法 - 我可以同时进行其他同步调用吗?假设我还需要在同一时间段内获取服务票证和项目票证 - 但在全部返回之前不要进行任何逻辑处理。我可以让所有三个调用异步运行吗?

这是目标

var startDate = DateTime.Now.AddDays(-30);
var endDate = DateTime.Now;

var timeTask = ListTimeEntries(startDate, endDate);
var serviceTask = ListServiceTickets(startDate, endDate);
var projectTask = ListProjectTickets(startdDate, endDate);

var timeEntries = await timeTask;
var serviceTickets = await serviceTask;
var projectTickets = await projectTask;

// Do what ever processing logic I need now.

如何实现?

对于 example #1 - 只是将 Method 设置为 async 工作吗?例如:

public async Task<List<TimeEntry>> ListTimeEntries(DateTime startDate, DateTime endDate)
{
    var page = 1;
    var hasMore = false;
    var qry = string.Format("timeStart >= [{0}] and timeEnd <= [{1}]", startDate, endDate); //For example
    var results = List<TimeEntry>();
    do
    {
        var newItems = api.GetEntries(qry, "id desc", maxPageSize, page).GetResults<List<TimeEntry>>();
        results.AddRange(newItems);
        hasMore = newItems.Any();
        page++;
    } while (hasMore);
    return results;
}

或者实际使用SDK的GetResultsAsync&lt;&gt;方法如示例2有什么好处:

public async Task<List<TimeEntry>> ListTimeEntries(DateTime startDate, DateTime endDate)
{
    var page = 1;
    var hasMore = false;
    var qry = string.Format("timeStart >= [{0}] and timeEnd <= [{1}]", startDate, endDate); //For example
    var results = List<TimeEntry>();
    do
    {
        var newItemsTask = api.GetEntries(qry, "id desc", maxPageSize, page).GetResultsAsync<List<TimeEntry>>();
        var newItems = await newItemsTask;
        results.AddRange(newItems);
        hasMore = newItems.Any();
        page++;
    } while (hasMore);
    return results;
}

虽然我知道这些方法在内部仍在同步运行 - 我希望每次调用 ListTimeEntriesListServiceTicketsListProjectTickets 时都异步进行以加快速度。

我想我的想法/希望 - 由于缺乏知识 - 每个同步调用都将使用不同的线程 - 从而加快整个过程。我离基地很远吗?

【问题讨论】:

  • 这是什么应用程序? api.GetEntries 是否绑定了 I/O,它是否返回 Task&lt;T&gt;?这两个问题决定了使用async/await 是否有好处以及是否可以使用async/await。至于您最终设想的调用代码,您必须检查 api 是否也是线程安全的(支持多个同时调用)。并非所有 API 都像 Entity Framework 的 DbContext 那样。
  • @Igor - 是的,api.GetEntries 将返回一个 Task&lt;T&gt; - 因为执行它的 GetResultsAsync&lt;T&gt; 调用。
  • 那么代码应该是var newItems = await api.GetEntries(... 并将async 关键字添加到调用它的方法签名中,并让它也返回一个Task&lt;T&gt;
  • 仅供参考,在您等待每个电话的“这是目标”部分。这意味着第二次调用将在第一次调用完成之前运行,第三次调用直到第二次调用。如果您希望它们全部同时发生,然后等待它们全部完成,您需要等待 Task.WhenAll 传入您的三个任务
  • @Igor 对不起,我误读了代码,这是一个漫长的星期五。 OP请忽略我的评论

标签: c# loops asynchronous async-await


【解决方案1】:

我会做如下的事情。您将需要了解有关 API 限制的潜在问题,当然,在所有 4 个线程完成之前至少有三个额外的请求在进行中的可能性 - 如果您有很多页面要检索,则需要付出很小的代价。

public async Task<IList<TimeEntry>> CallSomething(DateTime startDate, DateTime endDate)
{
    var qry = string.Format("timeStart >= [{0}] and timeEnd <= [{1}]", startDate, endDate); //For example

    const threadCount = 4;

    var isComplete = new ManualResetEvent();
    var page = 0;
    var results = new ConcurrentBag<TimeEntry>();
    var thread = new Task[threadCount];
    for(var i = 0; i < threadCount; ++i)
    {
        thread[i] = Task.Run(
            async () =>
            {
                while(!isComplete.WaitOne(TimeSpan.Zero))
                {
                    var localPage = Interlocked.Increment(ref page);

                    var newItems = await api
                        .GetEntries(qry, "id desc", maxPageSize, localPage)
                        .GetResultsAsync<List<TimeEntry>>()
                        .ConfigureAwait(false);

                    if (!newItems.Any())
                    {
                        isComplete.Set();
                    }
                    else
                    {
                        foreach (var item in newItems)
                        {
                            results.Add(item);
                        }
                    }
                }
            })
        )
    }

    await Task.WhenAll(thread).ConfigureAwait(false);

    return results.OrderByDescending(f => f.Id).ToList();
}

【讨论】:

    猜你喜欢
    • 2014-12-04
    • 2016-05-14
    • 2015-04-18
    • 1970-01-01
    • 2015-03-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多