我有以下情况(或与async await机制的基本误解)。
假设您有一组需要很长时间的1-20网络请求呼叫:findItemsByProduct()
。
你想在异步请求中包装它,这将能够将所有这些调用抽象为一个异步调用,但我似乎无法在不使用更多线程的情况下完成它。
如果我在做:
int total = result.paginationOutput.totalPages;
for (int i = 2; i < total + 1; i++)
{
await Task.Factory.StartNew(() =>
{
result = client.findItemsByProduct(i);
});
newList.AddRange(result.searchResult.item);
}
}
return newList;
这里的问题是,呼叫不会一起运行,而是一个接一个地等待。 我希望所有的呼叫能够一起运行,而不是收获结果。
作为伪代码,我希望代码运行如下:
forEach item {
result = item.makeWebRequest();
}
foreach item {
List.addRange(item.harvestResults);
}
我不知道如何让代码执行此操作..
答案 0 :(得分:1)
鉴于您的要求,我认为:
我会使用CountdownEvent来解决这个问题。
var results = new ConcurrentBag<ItemType>(result.pagination.totalPages);
using (var e = new CountdownEvent(result.pagination.totalPages))
{
for (int i = 2; i <= result.pagination.totalPages+1; i++)
{
Task.Factory.StartNew(() => return client.findItemsByProduct(i))
.ContinueWith(items => {
results.AddRange(items);
e.Signal(); // signal task is done
});
}
// Wait for all requests to complete
e.Wait();
}
// Process results
foreach (var item in results)
{
...
}
答案 1 :(得分:1)
理想情况下,您应添加一个返回findItemsByProductAsync
的{{1}}。这样,您就不必使用Task<Item[]>
或StartNew
创建不必要的任务。
然后你的代码看起来像这样:
Task.Run
答案 2 :(得分:0)
这个特殊问题很容易解决,甚至没有使用await
。只需创建每个任务,将所有任务放入列表中,然后使用该列表上的WhenAll
来获取表示所有这些任务完成的任务:
public static Task<Item[]> Foo()
{
int total = result.paginationOutput.totalPages;
var tasks = new List<Task<Item>>();
for (int i = 2; i < total + 1; i++)
{
tasks.Add(Task.Factory.StartNew(() => client.findItemsByProduct(i)));
}
return Task.WhenAll(tasks);
}
另请注意,您在代码中使用result
的方式存在严重问题。您使用相同的变量完成了每个不同的任务,因此存在关于它是否正常工作的竞争条件。您可能最终添加两次相同的调用并完全跳过一次。相反,您应该将findItemsByProduct
的调用作为任务的结果,并使用该任务的Result
。
答案 3 :(得分:0)
如果要正确使用async-await,则必须将函数声明为异步,并且调用您的函数也必须是异步的。这将继续,直到您有一次启动异步进程的同步函数。
你的功能如下:
顺便说一下,你没有描述清单中的内容。我认为他们是 在这种情况下,类型为T.的对象result.SearchResult.Item返回 IEnumerable的
private async Task<List<T>> FindItems(...)
{
int total = result.paginationOutput.totalPages;
var newList = new List<T>();
for (int i = 2; i < total + 1; i++)
{
IEnumerable<T> result = await Task.Factory.StartNew(() =>
{
return client.findItemsByProduct(i);
});
newList.AddRange(result.searchResult.item);
}
return newList;
}
如果你这样做,你的函数将是异步的,但findItemsByProduct将一个接一个地执行。如果要同时执行它们,则不应等待结果,而是在上一个任务完成之前启动下一个任务。一旦所有任务开始,等待所有任务完成。像这样:
private async Task<List<T>> FindItems(...)
{
int total = result.paginationOutput.totalPages;
var tasks= new List<Task<IEnumerable<T>>>();
// start all tasks. don't wait for the result yet
for (int i = 2; i < total + 1; i++)
{
Task<IEnumerable<T>> task = Task.Factory.StartNew(() =>
{
return client.findItemsByProduct(i);
});
tasks.Add(task);
}
// now that all tasks are started, wait until all are finished
await Task.WhenAll(tasks);
// the result of each task is now in task.Result
// the type of result is IEnumerable<T>
// put all into one big list using some linq:
return tasks.SelectMany ( task => task.Result.SearchResult.Item)
.ToList();
// if you're not familiar to linq yet, use a foreach:
var newList = new List<T>();
foreach (var task in tasks)
{
newList.AddRange(task.Result.searchResult.item);
}
return newList;
}