我有以下代码通过文件循环并一次读取1024个字节。第一次迭代使用FileStream.Read()
,第二次迭代使用FileStream.ReadAsync()
。
private async void Button_Click(object sender, RoutedEventArgs e)
{
await Task.Run(() => Test()).ConfigureAwait(false);
}
private async Task Test()
{
Stopwatch sw = new Stopwatch();
sw.Start();
int readSize;
int blockSize = 1024;
byte[] data = new byte[blockSize];
string theFile = @"C:\test.mp4";
long totalRead = 0;
using (FileStream fs = new FileStream(theFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
readSize = fs.Read(data, 0, blockSize);
while (readSize > 0)
{
totalRead += readSize;
readSize = fs.Read(data, 0, blockSize);
}
}
sw.Stop();
Console.WriteLine($"Read() Took {sw.ElapsedMilliseconds}ms and totalRead: {totalRead}");
sw.Reset();
sw.Start();
totalRead = 0;
using (FileStream fs = new FileStream(theFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, (blockSize*2), FileOptions.Asynchronous | FileOptions.SequentialScan))
{
readSize = await fs.ReadAsync(data, 0, blockSize).ConfigureAwait(false);
while (readSize > 0)
{
totalRead += readSize;
readSize = await fs.ReadAsync(data, 0, blockSize).ConfigureAwait(false);
}
}
sw.Stop();
Console.WriteLine($"ReadAsync() Took {sw.ElapsedMilliseconds}ms and totalRead: {totalRead}");
}
结果:
Read() Took 162ms and totalRead: 162835040
ReadAsync() Took 15597ms and totalRead: 162835040
ReadAsync()大约慢100倍。我错过了什么吗?我唯一能想到的是使用ReadAsync()创建和销毁任务的开销,但开销是多少?
更新
我已经更改了上面的代码以反映@Cory的建议。有一点改善:
Read() Took 142ms and totalRead: 162835040
ReadAsync() Took 12288ms and totalRead: 162835040
当我按照@Alexandru的建议将读取块大小增加到1MB时,结果更加可以接受:
Read() Took 32ms and totalRead: 162835040
ReadAsync() Took 76ms and totalRead: 162835040
所以,它向我暗示,这确实是造成缓慢的任务数量的开销。但是,如果任务的创建和破坏仅需要100μs,那么在块大小较小的情况下,事情仍然没有真正加速。
答案 0 :(得分:5)
您的方法签名表明您是从WPF应用程序执行此操作。虽然阻塞代码将在此期间占用UI线程,但每次异步操作完成时,异步代码将被强制通过UI消息队列,从而减慢它并与任何UI消息竞争。你应该尝试从UI线程中删除它,如下所示:
void Button_Click(object sender, RoutedEventArgs e)
{
Task.Run(() => Button_Click_Impl());
}
async Task Button_Click_Impl()
{
// put code here.
}
接下来,以异步模式打开文件。如果不这样做,则会模拟异步并且速度会慢得多:
new FileStream(theFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 4096,
FileOptions.Asynchronous | FileOptions.SequentialScan)
最后,您还可以使用ConfigureAwait(false)
提取一些小的性能,以避免在线程之间移动:
readSize = await fs.ReadAsync(data, 0, 1024).ConfigureAwait(false);
答案 1 :(得分:0)
单个ReadAsync
操作的开销远高于单个Read
操作(特别是如果在打开文件时没有使用正确的模式,请参阅其他答案)。如果最终最终以内存中的整个文件结束,只需查询文件的大小,分配足够大的缓冲区并一次读取所有文件。否则,您仍然可以将缓冲区大小增加到例如32 MiB,如果您期望更大的文件大小,甚至更大。这应该可以大大加快一切。
如果每个块都有相当大的CPU限制工作,那么只需启动新任务即可。否则,UI应该通过ReadAsync
操作(具有足够大的缓冲区)保持响应时间(如果它立即完成,您可能仍然阻止UI,请参阅Task.Yield()
处的备注)。 / p>