如何在循环中使用await

时间:2013-10-17 15:53:20

标签: c# .net async-await

我正在尝试创建一个异步控制台应用程序,它可以对集合进行一些操作。我有一个版本使用并行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;
    }
}

6 个答案:

答案 0 :(得分:95)

您使用await关键字的方式告诉C#您希望每次通过循环时等待,这不是并行的。您可以通过存储Task的列表,然后使用Task.WhenAll await全部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); } 来重写您的方法以执行您想要的操作。

{{1}}

答案 1 :(得分:31)

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

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

return Task.WhenAll(list.Select(DoWorkAsync));

答案 2 :(得分:9)

public async Task<bool> Init()
{
    var series = Enumerable.Range(1, 5);
    Task.WhenAll(series.Select(i => DoWorkAsync(i)));
    return true;
}

答案 3 :(得分:1)

在C#7.0 you can use semantic names to each of the members of the 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.内的foreach

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

答案 4 :(得分:0)

为了补充这里已经很好的答案,记住异步方法返回一个 Task 对我总是有帮助的。

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

认为 await 只是一个魔术字,它会导致保存执行状态,然后跳到下一个可用的行直到准备好,这会助长混淆:“为什么 for 循环不只是跳过带有 await 的行并转到下一个语句?”

如果相反,你认为 await 更像是一个 yield 语句,当它把控制权返回给调用者时,它会带来一个 Task,在我看来,流程开始变得更有意义:“for 循环停止在等待,并将控制权和 Task 返回给调用者。在完成之前 for 循环不会继续。"

答案 5 :(得分:-1)

有时候,我们需要的只是一个复制 + 粘贴就绪的解决方案,因此,这是Tim.S.的整个解决方案:

        with open("myBlock.json") as json_data:
            self.myBlockInfo = json.load(json_data)

        origData = self.myBlockInfo["myData"]
        origLine = '\"myData\":\"'+origData +'\",'

        nowData = self.timeISO8601ZuluUTC()
        newLine = '\"myData\":\"'+nowData+'\",'

        with open("myBlock.json", "r+") as fh:
            for line in fh.readlines():
                if origLine in line:
                    print ("1-->", line)
                    str.replace(line, origLine, newLine)
                    print("2-->", line)

然后在控制台中显示结果:

enter image description here