【问题标题】:creating a .net async wrapper to a sync request为同步请求创建 .net 异步包装器
【发布时间】:2015-08-06 20:57:35
【问题描述】:

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

假设您有一组需要很长时间的 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);
}

我不知道如何编写代码来做到这一点..

【问题讨论】:

  • 您从哪里想到使用Task.Factory.StartNew
  • 您的数据访问组件是否有任何异步方法可以用来异步实现findItemsByProduct
  • 如果您想同时(并发)运行多个操作,您实际上是在问如何并行运行它们,这是与异步不同(但互补)的想法代码。

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


【解决方案1】:

理想情况下,您应该添加一个返回Task&lt;Item[]&gt;findItemsByProductAsync。这样,您就不必使用StartNewTask.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();

【讨论】:

    【解决方案2】:

    鉴于您的要求,我认为:

    • 处理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) 
    {
        ...
    }
    

    【讨论】:

    • 这对于那些不能使用 async 和 await 语言特性的人来说是一个很好的答案。
    【解决方案3】:

    即使不使用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

    【讨论】:

      【解决方案4】:

      如果你想正确使用 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;
      }
      

      【讨论】:

        猜你喜欢
        • 2011-10-26
        • 2011-02-07
        • 2014-12-30
        • 1970-01-01
        • 2016-08-19
        • 2023-04-01
        • 1970-01-01
        • 1970-01-01
        • 2011-10-25
        相关资源
        最近更新 更多