异步文件读取速度比同步或手动线程慢40倍

时间:2019-02-18 18:29:23

标签: c# async-await

我有3个文件,每个文件100万行,正在逐行读取它们。没有处理,只是在阅读,因为我只是在试用。

如果我同步执行此操作,则需要1秒钟。如果我改用Threads(每个文件一个),则速度会更快(代码不在下面,但我只是创建了一个新的Thread并为每个文件启动了它)。

当我更改为异步时,它的耗时是40秒的40倍。如果我添加任何工作来进行实际处理,则无法看到如何在同步上使用异步,或者我是否想要使用线程的响应式应用程序。

还是我在这段代码中做了根本上错误的事情,而不是异步的意图?

谢谢。

class AsyncTestIOBound
{
    Stopwatch sw = new Stopwatch();
    internal void Tests()
    {
        DoSynchronous();
        DoASynchronous();
    }
    #region sync
    private void DoSynchronous()
    {
        sw.Restart();
        var start = sw.ElapsedMilliseconds;
        Console.WriteLine($"Starting Sync Test");
        DoSync("Addresses", "SampleLargeFile1.txt");
        DoSync("routes   ", "SampleLargeFile2.txt");
        DoSync("Equipment", "SampleLargeFile3.txt");
        sw.Stop();
        Console.WriteLine($"Ended Sync Test. Took {(sw.ElapsedMilliseconds - start)} mseconds");
        Console.ReadKey();
    }

    private long DoSync(string v, string filename)
    {
        string line;
        long counter = 0;
        using (StreamReader file = new StreamReader(filename))
        {
            while ((line = file.ReadLine()) != null)
            {
                counter++;
            }
        }
        Console.WriteLine($"{v}: T{Thread.CurrentThread.ManagedThreadId}: Lines: {counter}");
        return counter;
    }
    #endregion

    #region async
    private void DoASynchronous()
    {
        sw.Restart();
        var start = sw.ElapsedMilliseconds;
        Console.WriteLine($"Starting Sync Test");
        Task a=DoASync("Addresses", "SampleLargeFile1.txt");
        Task b=DoASync("routes   ", "SampleLargeFile2.txt");
        Task c=DoASync("Equipment", "SampleLargeFile3.txt");
        Task.WaitAll(a, b, c);
        sw.Stop();
        Console.WriteLine($"Ended Sync Test. Took {(sw.ElapsedMilliseconds - start)} mseconds");
        Console.ReadKey();
    }

    private async Task<long> DoASync(string v, string filename)
    {
        string line;
        long counter = 0;
        using (StreamReader file = new StreamReader(filename))
        {
            while ((line = await file.ReadLineAsync()) != null)
            {
                counter++;
            }
        }
        Console.WriteLine($"{v}: T{Thread.CurrentThread.ManagedThreadId}: Lines: {counter}");
        return counter;
    }
    #endregion

}

2 个答案:

答案 0 :(得分:2)

几件事。首先,我将使用async方法一次读取所有行,以便您仅等待一次(而不是每行)。

private async Task<long> DoASync(string v, string filename)
{
    string lines;
    long counter = 0;
    using (StreamReader file = new StreamReader(filename))
    {
        lines = await reader.ReadToEndAsync();
    }
    Console.WriteLine($"{v}: T{Thread.CurrentThread.ManagedThreadId}: Lines: {lines.Split('\n').Length}");
    return counter;
}

接下来,您还可以分别等待每个任务。这将导致您的CPU一次只专注于一个,而不可能在三个之间切换,这将导致更多的开销。

private async void DoASynchronous()
{
    sw.Restart();
    var start = sw.ElapsedMilliseconds;
    Console.WriteLine($"Starting Sync Test");
    await DoASync("Addresses", "SampleLargeFile1.txt");
    await DoASync("routes   ", "SampleLargeFile2.txt");
    await DoASync("Equipment", "SampleLargeFile3.txt");
    sw.Stop();
    Console.WriteLine($"Ended Sync Test. Took {(sw.ElapsedMilliseconds - start)} mseconds");
    Console.ReadKey();
}

之所以看到较低的性能,是因为等待如何与CPU负载一起工作。对于每个新行,这将导致CPU使用率增加。异步机制增加了处理,分配和同步。另外,我们需要两次而不是一次地转换到内核模式(首先启动IO,然后出队IO完成通知)。

更多信息,请参见:Does async await increases Context switching

答案 1 :(得分:2)

由于您在一个巨大的循环中多次使用await(在您的情况下,循环通过“ SampleLargeFile”的每一行),因此您要进行大量的上下文切换,因此开销可能真的很糟糕

对于每一行,您的代码可能正在每个文件之间切换。如果您的计算机使用硬盘驱动器,则情况可能更糟。想象一下HD的头部变得疯狂。

使用普通线程时,您不会切换每行的上下文。

要解决此问题,只需一次运行即可读取文件。您仍然可以使用async/awaitReadToEndAsync())并获得良好的性能。

编辑

因此,您正在尝试使用异步计数文本文件上的行,对吧?

尝试此操作(无需将整个文件加载到内存中):

private async Task<int> CountLines(string path)
{
    int count = 0;
    await Task.Run(() =>
    {
        using (FileStream fs = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
        using (BufferedStream bs = new BufferedStream(fs))
        using (StreamReader sr = new StreamReader(bs))
        {
            while (sr.ReadLine() != null)
            {
                count++;
            }
        }
    });
    return count;
}