C# 如何在循环中使用wait

C# 如何在循环中使用wait,c#,.net,async-await,C#,.net,Async Await,我正在尝试创建一个异步控制台应用程序,它可以对集合进行一些工作。我有一个版本使用并行for循环,另一个版本使用异步/等待。我希望异步/等待版本的工作方式与并行版本类似,但它是同步执行的。我做错了什么 class Program { static void Main(string[] args) { var worker = new Worker(); worker.ParallelInit(); var t = worker.Ini

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

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;
    }
}
类程序
{
静态void Main(字符串[]参数)
{
var-worker=新的worker();
worker.ParallelInit();
var t=worker.Init();
t、 等待();
Console.ReadKey();
}
}
公社工人
{
公共异步任务Init()
{
var series=Enumerable.Range(1,5).ToList();
foreach(系列中的var i)
{
WriteLine(“启动进程{0}”,i);
var结果=等待DoWorkAsync(i);
如果(结果)
{
WriteLine(“结束进程{0}”,i);
}
}
返回true;
}
公共异步任务DoWorkAsync(int i)
{
Console.WriteLine(“working..{0}”,i);
等待任务。延迟(1000);
返回true;
}
公共bool ParallelInit()
{
var series=Enumerable.Range(1,5).ToList();
Parallel.ForEach(系列,i=>
{
WriteLine(“启动进程{0}”,i);
DOWORKSync(i);
WriteLine(“结束进程{0}”,i);
});
返回true;
}
}

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

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

return Task.WhenAll(list.Select(DoWorkAsync));
公共异步任务初始化()
{
var系列=可枚举范围(1,5);
Task.WhenAll(series.Select(i=>doworksync(i));
返回true;
}

使用
wait
关键字的方式告诉C#每次通过循环时都要等待,这是不平行的。通过存储
Task
s的列表,然后
wait
等待它们,您可以像这样重写您的方法来执行您想要的操作

公共异步任务初始化()
{
var series=Enumerable.Range(1,5).ToList();
var tasks=新列表();
foreach(系列中的var i)
{
WriteLine(“启动进程{0}”,i);
添加(DoWorkAsync(i));
}
foreach(等待任务中的var任务。WhenAll(任务))
{
如果(任务项2)
{
WriteLine(“结束进程{0}”,task.Item1);
}
}
返回true;
}
公共异步任务DoWorkAsync(int i)
{
Console.WriteLine(“working..{0}”,i);
等待任务。延迟(1000);
返回Tuple.Create(i,true);
}
在C#7.0中,以下是使用新语法的答案:

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

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

所以在这个问题的例子中,循环的每个迭代都有
wait
。这会导致
Init()
方法使用
任务将控件返回给其调用者,而不是bool

将await看作是一个神奇的词,它会导致保存执行状态,然后跳到下一行直到准备就绪,这会引起混淆:“为什么for循环不使用await跳过这一行并转到下一个语句?”


相反,如果您认为wait更像一个yield语句,当它将控制权返回给调用者时,它会带来一个
任务
,那么在我看来,流开始变得更有意义:“for循环在wait时停止,并将控制权和
任务
返回给调用者。在这之前for循环不会继续。”

所以你不能在任何循环中使用任何异步方法?@Satish:你可以。然而,
await
与您想要的正好相反–它等待
任务完成。我想接受您的答案,但Tims有一个更好的答案。或者,如果您不需要知道任务何时完成,您可以直接调用方法,而不必等待它们来确认语法在做什么-它正在运行调用的任务
doworksync
列表中的每个项目上(将每个项目传递到
doworksync
,我假设它只有一个参数)?我不知道其他项目,但是对于并行循环来说,/foreach的并行似乎更直接。需要注意的是,当您看到
结束进程时,
通知并不是任务实际结束的时间。所有这些通知都会在最后一个任务完成后按顺序转储。当您看到“结束流程1”时,流程1可能已经结束了很长一段时间。除了在这里选择的单词外,+1@Brettski I可能是错误的,但是并行循环会捕获任何类型的异步结果。通过返回一个任务,您可以立即返回一个任务对象,您可以在其中管理内部正在进行的工作,例如取消任务或查看异常。现在使用Async/Await,您可以以更友好的方式处理Task对象,也就是说,您不必执行Task.Result。@Tim S,如果我想使用Tasks.WhenAll方法通过异步函数返回一个值怎么办?在
doworksync
中实现
Semaphore
以限制最大执行任务是否是一种不好的做法?官方文档对此有很多要说的,演示使用循环的一个地方是:
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 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);
}
// ...
foreach (var (IsDone, Index) in await Task.WhenAll(tasks))
{
    if (IsDone)
    {
        Console.WriteLine("Ending Process {0}", Index);
    }
}
// ...