Почему File.ReadAllLinesAsyn c () блокирует поток пользовательского интерфейса? - PullRequest
1 голос
/ 02 августа 2020

Вот мой код. Обработчик событий для кнопки WPF, которая читает строки файла:

private async void Button_OnClick(object sender, RoutedEventArgs e)
{
    Button.Content = "Loading...";
    var lines = await File.ReadAllLinesAsync(@"D:\temp.txt"); //Why blocking UI Thread???
    Button.Content = "Show"; //Reset Button text
}

Я использовал асинхронную версию File.ReadAllLines() метода в приложении NET Core 3.1 WPF.

Но это блокирование потока пользовательского интерфейса! Почему?

Обновление : То же, что и @Theodor Zoulias, я провожу тест:

private async void Button_OnClick(object sender, RoutedEventArgs e)
    {
        Button.Content = "Loading...";
        TextBox.Text = "";

        var stopwatch = Stopwatch.StartNew();
        var task = File.ReadAllLinesAsync(@"D:\temp.txt"); //Problem
        var duration1 = stopwatch.ElapsedMilliseconds;
        var isCompleted = task.IsCompleted;
        stopwatch.Restart();
        var lines = await task;
        var duration2 = stopwatch.ElapsedMilliseconds;

        Debug.WriteLine($"Create: {duration1:#,0} msec, Task.IsCompleted: {isCompleted}");
        Debug.WriteLine($"Await:  {duration2:#,0} msec, Lines: {lines.Length:#,0}");


        Button.Content = "Show";
    }

результат:

Create: 652 msec msec, Task.IsCompleted: False | Await:   15 msec, Lines: 480,001

. NET Core 3.1, C# 8, WPF, отладочная сборка | 7.32 Мб Файл (.txt) | Жесткий диск 5400 SATA

1 Ответ

3 голосов
/ 02 августа 2020

К сожалению, встроенные асинхронные API-интерфейсы для доступа к файловой системе не реализованы последовательно в соответствии с собственными рекомендациями Microsoft о том, как должны вести себя асинхронные методы.

Асинхронный метод основанный на TAP, может выполнять небольшой объем работы синхронно, например проверять аргументы и запускать асинхронную операцию, прежде чем он вернет результирующую задачу. Синхронная работа должна быть сведена к минимуму, чтобы асинхронный метод мог быстро возвращаться.

Такие методы, как StreamReader.ReadToEndAsync, не ведут себя таким образом и вместо этого блокируют текущий поток для значительное количество времени, прежде чем вернуть неполный Task. Например, в моем более старом эксперименте с чтением файла размером 6 МБ с моего SSD этот метод блокировал вызывающий поток на 120 мс c, возвращая Task, который затем был завершен только через 20 мс c. Мое предложение - избегать использования API-интерфейсов асинхронной файловой системы из приложений GUI и использовать вместо них синхронные API-интерфейсы, заключенные в Task.Run.

var lines = await Task.Run(() => File.ReadAllLines(@"D:\temp.txt"));

Обновление: Вот некоторые экспериментальные результаты с File.ReadAllLinesAsync:

var stopwatch = Stopwatch.StartNew();
var task = File.ReadAllLinesAsync(@"C:\6MBfile.txt");
var duration1 = stopwatch.ElapsedMilliseconds;
bool isCompleted = task.IsCompleted;
stopwatch.Restart();
var lines = await task;
var duration2 = stopwatch.ElapsedMilliseconds;
Console.WriteLine($"Create: {duration1:#,0} msec, Task.IsCompleted: {isCompleted}");
Console.WriteLine($"Await:  {duration2:#,0} msec, Lines: {lines.Length:#,0}");

Вывод:

Create: 450 msec, Task.IsCompleted: False
Await:  5 msec, Lines: 204,000

Метод File.ReadAllLinesAsync заблокировал текущий поток на 450 мс c, а возвращенная задача завершилась через 5 мс c. Эти измерения согласованы после нескольких запусков.

. NET Core 3.1.3, C# 8, консольное приложение, сборка выпуска (отладчик не подключен), Windows 10, SSD Toshiba OCZ Ar c 100 240 ГБ

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