根据.NET文档,异步IO命令可用于并行执行文件操作:https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/using-async-for-文件访问#并行异步 io
然而,对上面的一个小修改表明情况并非如此:
public static async Task SimpleParallelWriteByteAsync()
{
var rootFilePath = "C:/temp/asynctest2";
var fileSize = 1024 * 1024 * 100;
if (Directory.Exists(rootFilePath))
Directory.Delete(rootFilePath, true);
Directory.CreateDirectory(rootFilePath);
var buffer = new byte[fileSize];
new Random().NextBytes(buffer);
IList<Task> writeTaskList = new List<Task>();
Stopwatch sw = Stopwatch.StartNew();
for (int index = 0; index <= 10; index += 1)
{
string fileName = $"file-{index:00}.txt";
string filePath = Path.Combine(rootFilePath, fileName);
writeTaskList.Add(File.WriteAllBytesAsync(filePath, buffer));
}
var timespanLoop = sw.Elapsed;
await Task.WhenAll(writeTaskList);
var timespanAfterWhenall = sw.Elapsed;
Console.WriteLine($"loop {timespanLoop} after when all {timespanAfterWhenall}");
}
其输出将返回: 当全部 00:00:05.6254815 后循环 00:00:05.6234609
但是,将一行转换为:
writeTaskList.Add(Task.Run(() => File.WriteAllBytesAsync(filePath, buffer)));
将为我们提供预期的并行输出:
loop 00:00:00.0089046 after when all 00:00:01.2339927
我的问题是是什么在控制这个?为什么 .NET 文档是错误的/具有误导性的?在我的理解中,异步文件操作不是在线程中执行的,而是向操作系统发送调用以异步写入文件,然后操作系统会在完成时通知.NET,如下所示:https:// /blog.stephencleary.com/2013/11/there-is-no-thread.html
这对于并行或其他方式运行的任务来说不是问题。它也与线程无关。相反,您在第一个示例中根本没有开始任务。
重要的是要注意,
Task.WhenAll
实际上并没有为您启动任务。它只是等待那些已经开始的任务完成。
这解释了为什么您的第一个代码示例在
WhenAll
之前和之后具有几乎相同的时间戳。您已创建列表中的任务,但尚未启动它们。 WhenAll
几乎可以立即有效地完成,因为没有任何事情需要等待。事实上,如果您检查输出文件,您可能会发现它们不在那里。 (除非它们是上次跑步的宿醉。)
而在修改后的示例中,您在将每个任务添加到列表之前显式启动每个任务(使用
Task.Run
)。当他们点击 Task.WhenAll
时,他们就已经开始了。
我认为误导的不是文档,而是运行 async 和运行 async/awaited 之间的区别。在原始代码中,文件写入确实是异步完成的,在它自己的线程上与正在等待它的上下文分开。在修改后的代码中,您正在触发任务,但不等待结果。
我认为人们在异步/等待模式中错过的是它创建了线程代码,但保留了人们更熟悉的同步编程实践。
例如,如果您观察线程计数,则修改后的版本将为循环中的每个“索引”生成一个新线程,而无需等待结果,而在原始版本中,它将为循环的每次迭代生成一个额外的线程,然后等待完成。