简体   繁体   English

如何在循环中使用 await

[英]How to use await in a loop

I'm trying to create an asynchronous console app that does a some work on a collection.我正在尝试创建一个异步控制台应用程序来对集合执行一些操作。 I have one version which uses parallel for loop another version that uses async/await.我有一个版本使用并行循环,另一个版本使用异步/等待。 I expected the async/await version to work similar to parallel version but it executes synchronously.我希望 async/await 版本的工作方式类似于并行版本,但它是同步执行的。 What am I doing wrong?我究竟做错了什么?

public class Program 
{
    public 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;
    }

}

The way you're using the await keyword tells C# that you want to wait each time you pass through the loop, which isn't parallel.您使用await关键字的方式告诉 C# 您希望每次通过循环时都等待,这不是并行的。 You can rewrite your method like this to do what you want, by storing a list of Task s and then await ing them all with Task.WhenAll .你可以像这样重写你的方法来做你想做的事,通过存储一个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);
}

Your code waits for each operation (using await ) to finish before starting the next iteration.您的代码在开始下一次迭代之前等待每个操作(使用await )完成。
Therefore, you don't get any parallelism.因此,您不会获得任何并行性。

If you want to run an existing asynchronous operation in parallel, you don't need await ;如果要并行运行现有的异步操作,则不需要await you just need to get a collection of Task s and call Task.WhenAll() to return a task that waits for all of them:您只需要获取Task的集合并调用Task.WhenAll()以返回等待所有这些的任务:

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

In C# 7.0 you can use semantic names to each of the members of the tuple , here is Tim S. 's answer using the new syntax:在 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);
}

You could also get rid of task.你也可以摆脱task. inside foreach :里面foreach

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

We can use async method in foreach loop to run async API calls.我们可以在 foreach 循环中使用异步方法来运行异步 API 调用。

public static void Main(string[] args)
{

List<ZoneDetails> lst = GetRecords();

    foreach (var item in lst)
    {
        //For loop run asyn
        var result = GetAPIData(item.ZoneId, item.fitnessclassid).Result;
        if (result != null && result.EventHistoryId != null)
        {
            UpdateDB(result);
        }
    }
}

private static async Task<FODBrandChannelLicense> GetAPIData(int zoneId, int fitnessclassid)
{

    HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

    var response = HttpClient.GetAsync(new Uri(url)).Result;

    var content = response.Content.ReadAsStringAsync().Result;
    var result = JsonConvert.DeserializeObject<Model>(content);

    if (response.EnsureSuccessStatusCode().IsSuccessStatusCode)
    {
        Console.WriteLine($"API Call completed successfully");

    }

    return result;
}

To add to the already good answers here, it's always helpful to me to remember that the async method returns a Task .为了补充这里已经很好的答案,记住 async 方法返回一个Task总是对我有帮助。

So in the example in this question, each iteration of the loop has await .所以在这个问题的例子中,循环的每次迭代都有await This causes the Init() method to return control to its caller with a Task<bool> - not a bool.这会导致Init()方法使用Task<bool>而非 bool将控制权返回给其调用者

Thinking of await as just a magic word that causes execution state to be saved, then skipped to the next available line until ready, encourages confusion: "why doesn't the for loop just skip the line with await and go to the next statement?"认为 await 只是一个导致执行状态被保存的魔法词,然后跳到下一个可用的行直到准备好,这会鼓励混淆:“为什么 for 循环不只是跳过带有 await 的行并转到下一条语句? ”

If instead you think of await as something more like a yield statement, that brings a Task with it when it returns control to the caller, in my opinion flow starts to make more sense: "the for loop stops at await, and returns control and the Task to the caller. The for loop won't continue until that is done."相反,如果你认为 await 更像是一个 yield 语句,当它把控制返回给调用者时,它会带来一个Task ,在我看来,流程开始变得更有意义:“for 循环在 await 处停止,并返回控制和调用者的Task 。在完成之前,for 循环不会继续。”

Sometimes, all we need is a copy + paste ready solution, thus, this is the whole solution of Tim.S. 有时,我们所需要的只是一个可复制 + 粘贴的解决方案,因此,这就是Tim.S的整个解决方案。 :

namespace AsyncSimple
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;

    class Startup
    {
        static void Main()
        {
            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();
            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);
        }
        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;
        }
    }
}

And the result in the console: 结果在控制台中:

在此处输入图片说明

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM