2

我有以下情况(或对异步等待机制的基本误解)。

假设您有一组需要很长时间的 1-20 个 Web 请求调用: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

4 回答 4

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) 
{
    ...
}
于 2013-06-25T15:46:53.217 回答
1

理想情况下,您应该添加 afindItemsByProductAsync返回 a Task<Item[]>。这样,您不必使用StartNewor创建不必要的任务Task.Run

然后您的代码可能如下所示:

int total = result.paginationOutput.totalPages;

// Start all downloads; each download is represented by a task.
Task<Item[]>[] tasks = Enumerable.Range(2, total - 1)
    .Select(i => client.findItemsByProductAsync(i)).ToArray();

// Wait for all downloads to complete.
Item[][] results = await Task.WhenAll(tasks);

// Flatten the results into a single collection.
return results.SelectMany(x => x).ToArray();
于 2013-06-25T16:46:57.043 回答
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.

于 2013-06-25T16:02:27.020 回答
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;
}
于 2015-08-06T12:57:35.250 回答