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