首页 > 解决方案 > 如何防止在 IO 操作期间由于属性快速变化而阻塞 UI?

问题描述

我正在开发一个有时会删除文件夹的应用程序。为了向用户显示进度,我ProgressBar在视图中使用了一个。因此,我的 ViewModeldouble 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. 我该如何规避这个?

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

标签: c#wpfuser-interfacemvvmio

解决方案


似乎TryDeleteFile正在 UI 线程中执行。鉴于其当前的实现,它不是一个异步方法,不应该返回 aTask而是 a 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 的博客文章


推荐阅读