与Read()相比,FileStream.ReadAsync非常慢

时间:2016-09-06 15:51:15

标签: c# async-await task-parallel-library

我有以下代码通过文件循环并一次读取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,那么在块大小较小的情况下,事情仍然没有真正加速。

2 个答案:

答案 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>