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

Run multiple instances of same method asynchronously?

我的要求很奇怪。

我有 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+1).

之前,无需 等待 GetDataFor(i) 完成

换句话说我需要:

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;
    }
}

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

代码实际上没有任何意义。

How task will distinguish between different calls for awaiting? It is getting over-written.

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

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

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

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

我怀疑 OP 想要实现他根本没有实现的东西,而且他没有花足够的时间进行调试以意识到他再次单线程处理了整个循环。

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

在这样的循环内部等待会产生很多延续和比预期更多的开销,我相信包括在继续循环之前等待每个调用完成。

看看等待Task.WaitAll

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

您可以使用 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

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

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());
}

有几种不同的方法。

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

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

其次,您可以使其异步。要使某些东西真正异步,您需要从最低级别开始(在本例中为 "call a remote API" 和 "store to database")并首先使异步 。然后你可以使 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)
  );
}

使代码异步需要更多工作 - 必须更改更多代码 - 但它在可伸缩性和资源使用方面更好。

当使用 async await 时,您实际上是在说 "whilst waiting for this task to finish please go off and do some independent work that doesn't rely on this task"。由于您不关心等待 GetDataFor 完成,因此您真的不想使用 async await.

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

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

基本上,这假定您不需要等待 GetDataFor 完成再做任何其他事情,字面意思是 'fire and forget'.

关于 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