异步运行同一方法的多个实例?

时间:2016-02-11 15:49:53

标签: c# performance asynchronous async-await task

我的要求很奇怪。

SomeMethod()调用了GetDataFor()

public void SomeMethod()
{
    for(int i = 0; i<100; i++) {
        var data = GetDataFor(i);
    }
}

public data GetDataFor(int i) {
    //call a remote API
    //to generate data for i
    //store to database
    return data;
}

对于每个i,最终结果将始终不同。在致电GetDataFor(i)之前,无需等待 GetDataFor(i+1)完成。

换句话说,我需要:

    成功调用GetDataFor()后,
  • 立即为每个i+1 致电i (并行调用它们看起来不可能)
  • 等到所有 GetDataFor()的100个实例已完成运行
  • 离开SomeMethod()
  • 的范围

关注YK1's answer,我试图像这样修改它:

public async Task<void> SomeMethod()
{
    for(int i = 0; i < 100; i++) {
        var task = Task.Run(() => GetDataFor(i));
        var data = await task;
    }
}

它没有抛出任何错误,但我需要理解这背后的概念:

  • task如何区分await的不同调用?它越来越多了。
  • 这样做是否明显错误?那么,怎么做呢?

6 个答案:

答案 0 :(得分:9)

您可以使用Parallel.For

public void SomeMethod()
{
    Parallel.For(0, 100, i =>
    {
        var data = GetDataFor(i);
        //Do something
    });
}

public data GetDataFor(int i)
{
    //generate data for i
    return data;
}

修改

并行循环的语法与您已经知道的forforeach循环非常相似,但并行循环在具有可用内核的计算机上运行得更快。另一个不同之处在于,与顺序循环不同,并行循环的定义不是为并行循环定义的。步骤通常同时并行进行。有时,两个步骤的顺序与循环顺序时相反。唯一的保证是所有循环的迭代都将在循环结束时运行。

对于并行循环,并行程度不需要由代码指定。相反,运行时环境在尽可能多的内核上同时执行循环的步骤。无论有多少核心,循环都能正常工作。如果只有一个核心,则性能接近(相当于几个百分点)顺序等效。如果有多个核心,性能会提高;在许多情况下,性能与核心数量成比例地提高。

您可以看到更详细的解释here

答案 1 :(得分:2)

我会将每个任务添加到集合中,然后在循环之后等待整个集合。

在这样的循环中等待会产生大量的延续和更多的开销,包括等待每个调用完成后再继续循环我相信。

请等一下等待Task.WaitAll

如果相反,每个任务的值对于处理很重要,那么请查看等待Task.WhenAll,然后将每个任务的结果读入您的返回集合。

答案 2 :(得分:2)

有几种不同的方法。

首先,您可以保持同步并且只是并行执行它们(在不同的线程上)。并行LINQ优于Parallel ,如果要在继续之前收集调用方法中的所有结果:

public data[] SomeMethod()
{
  return Enumerable.Range(0, 100)
      .AsParallel().AsOrdered()
      .Select(GetDataFor).ToArray();
}

其次,你可以使它异步。要使某些东西真正异步,你需要从最低级别开始(在这种情况下,&#34;调用远程API&#34;以及&#34;存储到数据库&#34;)并首先进行异步 。然后你可以使GetDataFor异步:

public async Task<data> GetDataForAsync(int i)
{
  await .. //call a remote API asynchronously
  await .. //store to database asynchronously
  return data;
}

然后你也可以使SomeMethod异步:

public Task<data[]> SomeMethodAsync()
{
  return Task.WhenAll(
      Enumerable.Range(0, 100).Select(GetDataForAsync)
  );
}

使代码异步更有效 - 代码必须更改 - 但在可伸缩性和资源使用方面它更好。

答案 3 :(得分:2)

使用async await时,您基本上是在说#34;等待此任务完成时,请继续做一些不依赖于此的独立工作任务&#34 ;.由于您不在乎等待GetDataFor完成,因此您并不想使用async await

This previous question似乎与您的要求非常相似。考虑到这一点,我认为你应该能够做到这样的事情:

public void SomeMethod()
{
    Task.Run(() => GetDataFor(i));
}

基本上,这假设你不需要在做任何其他事情之前等待GetDataFor完成,它实际上是“开始并忘记”。

关于Parallel.For,只要你有超过1个核心,你可能会看到性能有所改善。如果没有,您可能会看到性能略有下降(更多开销)。 Here's an article有助于解释其工作原理。

<强> 更新

根据您的评论,我会建议:

var TList = new List<Task>();

for (var i = 0; i < 100; i++)
{
    TList.Add(Task.Run(() => GetDataFor(i)));
}

await Task.WhenAll(TList);     

Here's a useful question突出显示了您可能希望使用WhenAll而不是WaitAll的原因。

您可能希望在完成任务的状态中包含一些检查,以查看哪些失败(如果有)。有关示例,请参阅here

答案 4 :(得分:1)

代码实际上毫无意义。

  

任务如何区分不同的等待呼叫?它越来越好了   覆盖。

它不会被覆盖。因为...

for(int i = 0; i < 100; i++) {
    var task = Task.Run(() => GetDataFor(i));
    var data = await task;
}

这是在继续循环之前为每个请求完成等待。等待等待结束。

这意味着整个任务是无关紧要的 - 这里没有任何并行发生。你可以在没有任务的情况下减少一些小额开销。

我怀疑OP想要实现他根本没有做到的事情,他没有花足够的时间进行调试,以便意识到他已经单独完成了整个循环。

答案 5 :(得分:0)

虽然您的原始代码覆盖了这些值,但您似乎正在尝试合并并行操作的结果。如果是,请考虑使用Task.ContinueWith来处理返回值。您的代码看起来像这样:

public void SomeMethod()
    List<Task> tasks = new List<Task>();
    for (var i = 0; i < 100; i++)
    {
        tasks.Add(Task.Run(() => GetDataFor(i)).ContinueWith((antecedent) => {
            // Process the results here.
        }));
    }
    Task.WaitAll(tasks.ToArray());
}