105

我正在尝试创建一个对集合进行一些工作的异步控制台应用程序。我有一个版本使用并行 for 循环,另一个版本使用 async/await。我希望 async/await 版本的工作方式类似于并行版本,但它是同步执行的。我究竟做错了什么?

class Program
{
    static void Main(string[] args)
    {
        var worker = new Worker();
        worker.ParallelInit();
        var t = worker.Init();
        t.Wait();
        Console.ReadKey();
    }
}

public class Worker
{
    public async Task<bool> Init()
    {
        var series = Enumerable.Range(1, 5).ToList();
        foreach (var i in series)
        {
            Console.WriteLine("Starting Process {0}", i);
            var result = await DoWorkAsync(i);
            if (result)
            {
                Console.WriteLine("Ending Process {0}", i);
            }
        }

        return true;
    }

    public async Task<bool> DoWorkAsync(int i)
    {
        Console.WriteLine("working..{0}", i);
        await Task.Delay(1000);
        return true;
    }

    public bool ParallelInit()
    {
        var series = Enumerable.Range(1, 5).ToList();
        Parallel.ForEach(series, i =>
        {
            Console.WriteLine("Starting Process {0}", i);
            DoWorkAsync(i);
            Console.WriteLine("Ending Process {0}", i);
        });
        return true;
    }
}
4

5 回答 5

142

您使用await关键字的方式告诉 C# 您希望在每次通过循环时等待,这不是并行的。你可以像这样重写你的方法来做你想做的事,方法是存储一个Tasks 列表,然后awaitTask.WhenAll.

public async Task<bool> Init()
{
    var series = Enumerable.Range(1, 5).ToList();
    var tasks = new List<Task<Tuple<int, bool>>>();
    foreach (var i in series)
    {
        Console.WriteLine("Starting Process {0}", i);
        tasks.Add(DoWorkAsync(i));
    }
    foreach (var task in await Task.WhenAll(tasks))
    {
        if (task.Item2)
        {
            Console.WriteLine("Ending Process {0}", task.Item1);
        }
    }
    return true;
}

public async Task<Tuple<int, bool>> DoWorkAsync(int i)
{
    Console.WriteLine("working..{0}", i);
    await Task.Delay(1000);
    return Tuple.Create(i, true);
}
于 2013-10-17T16:06:03.577 回答
41

您的代码在开始下一次迭代之前等待每个操作(使用await)完成。
因此,您不会得到任何并行性。

如果要并行运行现有的异步操作,则不需要await; 您只需要获取Tasks 的集合并调用Task.WhenAll()以返回等待所有这些的任务:

return Task.WhenAll(list.Select(DoWorkAsync));
于 2013-10-17T15:56:13.270 回答
13
public async Task<bool> Init()
{
    var series = Enumerable.Range(1, 5);
    Task.WhenAll(series.Select(i => DoWorkAsync(i)));
    return true;
}
于 2013-10-17T16:05:47.263 回答
5

在 C# 7.0中,您可以对 tuple 的每个成员使用语义名称,这是Tim S.使用新语法的答案:

public async Task<bool> Init()
{
    var series = Enumerable.Range(1, 5).ToList();
    var tasks = new List<Task<(int Index, bool IsDone)>>();

    foreach (var i in series)
    {
        Console.WriteLine("Starting Process {0}", i);
        tasks.Add(DoWorkAsync(i));
    }

    foreach (var task in await Task.WhenAll(tasks))
    {
        if (task.IsDone)
        {
            Console.WriteLine("Ending Process {0}", task.Index);
        }
    }

    return true;
}

public async Task<(int Index, bool IsDone)> DoWorkAsync(int i)
{
    Console.WriteLine("working..{0}", i);
    await Task.Delay(1000);
    return (i, true);
}

你也可以摆脱task. insideforeach

// ...
foreach (var (IsDone, Index) in await Task.WhenAll(tasks))
{
    if (IsDone)
    {
        Console.WriteLine("Ending Process {0}", Index);
    }
}
// ...
于 2019-10-02T10:02:58.990 回答
0

为了在这里添加已经很好的答案,记住 async 方法返回一个Task对我总是有帮助的。

所以在这个问题的例子中,循环的每次迭代都有await. 这会导致该Init()方法使用Task<bool>- 而不是布尔值将控制权返回给其调用者。

将 await 视为一个能保存执行状态的魔术词,然后跳到下一个可用行直到准备好,这会引起混淆:“为什么 for 循环不直接跳过带有 await 的行并转到下一个语句? "

相反,如果您认为 await 更像是一个 yield 语句,Task当它将控制权返回给调用者时会带来 aTask给调用者。在完成之前,for循环不会继续。

于 2021-05-01T02:20:48.920 回答