чтение асинхронных файлов в 40 раз медленнее, чем синхронные или ручные потоки - PullRequest
0 голосов
/ 18 февраля 2019

У меня есть 3 файла, каждый по 1 миллиону строк, и я читаю их построчно.Никакой обработки, просто чтение, потому что я просто проверяю.

Если я делаю это синхронно, это занимает 1 секунду.Если я переключаюсь на использование потоков, по одному для каждого файла, это происходит немного быстрее (код не приведен ниже, но я просто создал новый поток и запустил его для каждого файла).

Когда я изменяю на асинхронный, этопринимая 40 раз дольше на 40 секунд.Если я добавлю какую-либо работу для фактической обработки, я не смогу понять, как бы я использовал асинхронный режим вместо синхронного или если бы я хотел адаптивное приложение, использующее потоки.

Или я делаю что-то в корне неправильно с этим кодом, а не так, как было задумано async?

Спасибо.

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 голосов
/ 18 февраля 2019

Поскольку вы используете await несколько раз в гигантском цикле (в вашем случае, циклически проходя по каждой строке "SampleLargeFile"), вы делаете много переключения контекста, и издержки могут быть очень плохими.

Для каждой строки ваш код может переключаться между файлами.Если ваш компьютер использует жесткий диск, это может стать еще хуже.Представьте себе, что голова вашего HD сходит с ума.

Когда вы используете нормальные потоки, вы не переключаете контекст для каждой строки.

Чтобы решить эту проблему, просто прочитайте файл за один прогон.Вы все еще можете использовать async/await (ReadToEndAsync()) и получить хорошую производительность.

РЕДАКТИРОВАТЬ

Итак, вы пытаетесь подсчитать строки в текстовом файле, используяasync, верно?

Попробуйте (не нужно загружать весь файл в память):

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;
}
0 голосов
/ 18 февраля 2019

несколько вещей.Сначала я прочитал бы все строки одновременно в асинхронном методе, так что вы ожидаете только один раз (вместо каждой строки).

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;
}

затем вы также можете ждать каждую задачу в отдельности.Это заставит ваш процессор сосредотачиваться только на одном за раз, вместо того, чтобы переключаться между 3, что приведет к дополнительным издержкам.

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();
}

Причина, по которой вы видите снижение производительности, связана с тем, как вы ожидаетеработает с загрузкой процессора.Для каждой новой строки это приведет к увеличению загрузки процессора.Асинхронное оборудование добавляет обработку, распределение и синхронизацию.Кроме того, нам нужно переходить в режим ядра два раза вместо одного (сначала для инициирования ввода-вывода, затем для исключения уведомления о завершении ввода-вывода).

Подробнее, см .: Увеличивается ли ожидание асинхронного переключения контекста

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...