如何在IO操作期间防止由于属性快速变化而阻塞UI?

时间:2019-05-03 13:04:43

标签: c# wpf user-interface mvvm io

我正在开发一个有时会删除文件夹的应用程序。为了向用户显示进度,我在视图中使用了ProgressBar。因此,我在ViewModel double SetupProgressdouble 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;
    }
}

我的问题是,在删除文件时,UI线程会完全阻塞,并且仅在操作完成后(所有文件都已删除)才会更新。

问题

  1. UI进程被阻止的原因可能是什么?
  2. 我该如何规避?

更新:删除了我在发布问题之前测试过的解决方案,因为它们不起作用,甚至无法解决问题的根源。

2 个答案:

答案 0 :(得分:3)

似乎TryDeleteFile正在UI线程中执行。考虑到当前的实现,它不是异步方法,不应返回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公开真正同步的方法是一种不好的做法。有关更多信息,请参阅Stephen Toub's blog post

答案 1 :(得分:0)

您的TryDeleteFile(string filePath)方法缺少 await 运算符,它将同步运行。

您可以将同步代码包装到异步调用中,更简单的方法是使用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));