Как предотвратить блокирование пользовательского интерфейса из-за быстро меняющихся свойств во время операций ввода-вывода? - PullRequest
0 голосов
/ 03 мая 2019

Я работаю над приложением, которое в какой-то момент удаляет папку. Чтобы отобразить прогресс для пользователя, я использую ProgressBar в представлении. Поэтому у меня есть два свойства в моей ViewModel double SetupProgress и double SetupProgressMax. Я компилирую список всех файлов, содержащихся в папке, которую нужно удалить, и каждый раз после успешного удаления файла я обновляю свойство SetupProgress. Я не знаю, сколько файлов нужно удалить заранее.

Мой код (сжатый до важных частей):

public class ViewModel : INotifyPropertyChanged
{
    public double SetupProgress
    {
        get; set; // Notifies about changes
    }

    public double SetupProgressMax
    {
        get; set; // Notifies about changes
    }

    public async Task<bool> DeleteFiles(IList<string> filesToBeDeleted)
    {
        bool success = true;
        SetupProgressMax = filesToBeDeleted.Count;

        foreach (string filePath in filesToBeDeleted)
        {
            success = success && await IOHelper.TryDeleteFile(filePath);

            if (success)
            {
                // Report that one item has been processed.
                _OnProgressChanged(); 
            }
            else
            {
                break;
            }
        }

        return success;
    }

    public void _OnProgressChanged()
    {
        // SetupProgress is the VM property bound by the ProgressBar
        SetupProgress++;
    }
}

public static class IOHelper
{
    public static async Task<bool> TryDeleteFile(string filePath, int tries = 3)
    {
        while (tries > 0)
        {
            try
            {
                FileInfo fi = new FileInfo(filePath);
                if (fi.IsReadOnly)
                {
                    fi.IsReadOnly = false;
                }
                fi.Delete();
                return true;
            }
            catch (FileNotFoundException)
            {
                return true;
            }
            catch (Exception ex)
            {
                tries--;
                if (tries == 0)
                {
                    // Log error
                }
                else
                {
                    // Log warning
                }
                await Task.Delay(50);
            }
        }

        return false;
    }
}

Моя проблема заключается в том, что во время удаления файлов поток пользовательского интерфейса полностью блокируется и будет обновляться только после завершения операции (все файлы были удалены).

Вопросы

  1. В чем может быть причина того, что процесс пользовательского интерфейса блокирует?
  2. Как я могу обойти это?

ОБНОВЛЕНИЕ: Удалены решения, которые я тестировал перед публикацией вопроса, поскольку они не работали и даже не могли устранить корень проблемы.

Ответы [ 2 ]

3 голосов
/ 03 мая 2019

Похоже, TryDeleteFile выполняется в потоке пользовательского интерфейса. Учитывая текущую реализацию, это не асинхронный метод, и он не должен возвращать Task, а bool:

public static bool TryDeleteFile(string filePath)
{
    try
    {
        FileInfo fi = new FileInfo(filePath);
        if (fi.IsReadOnly)
        {
            fi.IsReadOnly = false;
        }
        fi.Delete();
        return true;
    }
    catch (FileNotFoundException)
    {
        return true;
    }
    catch (Exception ex)
    {
        // Log Exception
        return false;
    }
}

Ключевое слово await совершенно не нужно, поскольку в методе отсутствуют какие-либо операции await.

Вы можете вызвать синхронный метод в фоновом потоке в вашей модели представления, используя Task.Run:

public async Task<bool> DeleteFiles(IList<string> filesToBeDeleted)
{
    ...
    foreach (string filePath in filesToBeDeleted)
    {
        success = success && await Task.Run(() => IOHelper.TryDeleteFile(filePath));
        ...
    }
    return success;
}

Обратите внимание, что показ метода, который является действительно синхронным, с использованием асинхронного API, считается плохой практикой. Пожалуйста, обратитесь к сообщению блога Стивена Туба для получения дополнительной информации.

0 голосов
/ 03 мая 2019

В вашем методе TryDeleteFile(string filePath) отсутствуют операторы , ожидающие , он будет работать синхронно.

Вы можете заключить синхронный код в асинхронный вызов, более простые методы - использовать Task.Run

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

Тамне является встроенной функцией для асинхронного удаления файла.Однако с помощью FileStream асинхронное удаление файла все еще можно выполнить.

public static bool TryDeleteFile(string filePath)
{
    try
    {
        var fi = new FileInfo(filePath);
        if (fi.IsReadOnly) fi.IsReadOnly = false;

        using (new FileStream(filePath, FileMode.Truncate, FileAccess.ReadWrite, FileShare.Delete, 1,
            FileOptions.DeleteOnClose | FileOptions.Asynchronous))
        {
        }

        return true;
    }
    catch (FileNotFoundException)
    {
        return true;
    }
    catch (Exception ex)
    {
                // Log Exception
        return false;
    }
}

Использование

await Task.Run(() => TryDeleteFile(filePath));
...