为同步请求创建.net异步包装器

时间:2013-06-25 15:25:46

标签: c# .net asynchronous task async-await

我有以下情况(或与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);
}

我不知道如何让代码执行此操作..

4 个答案:

答案 0 :(得分:1)

鉴于您的要求,我认为:

  • 处理 n 非阻塞任务的数量
  • 所有查询返回后的处理结果

我会使用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;
}