C# 使用Wait运行耗时任务时UI被阻止

C# 使用Wait运行耗时任务时UI被阻止,c#,wpf,multithreading,C#,Wpf,Multithreading,我想建立一个文件夹清理程序。预计它将实时向文本框控件报告已删除的文件。所以我在我的按钮点击事件中使用了wait Task.Run(()=>CleanFolder(folderPath,progress))函数。但是UI在运行时被阻塞。当CheanFolder()方法运行完成后,所有已删除的文件将同时显示 namespace FolderCleaner { public partial class MainWindow : Window { string folde

我想建立一个文件夹清理程序。预计它将实时向
文本框
控件报告已删除的文件。所以我在我的按钮点击事件中使用了
wait Task.Run(()=>CleanFolder(folderPath,progress))
函数。但是UI在运行时被阻塞。当
CheanFolder()
方法运行完成后,所有已删除的文件将同时显示

namespace FolderCleaner
{
    public partial class MainWindow : Window
    {
        string folderPath;
        string matchPattern;

        private void ButtonOpen_Click(object sender, RoutedEventArgs e)
        {
            FolderBrowserDialog fbd = new FolderBrowserDialog() { Description = "Select a folder" };
            if (fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                folderPath = fbd.SelectedPath;
                textBoxPath.Text = folderPath;
                buttonClean.IsEnabled = true;
                textBoxList.Text = "Folder path: " + folderPath + "\n";
            }
        }

        private async void ButtonClean_Click(object sender, RoutedEventArgs e)
        {
            matchPattern = textBoxPattern.Text;
            buttonOpen.IsEnabled = false;
            buttonClean.IsEnabled = false;
            Progress<string> progress = new Progress<string>(msg =>
            {
                textBoxList.AppendText("File deleted: " + msg + "\n");
                textBoxList.CaretIndex = textBoxList.Text.Length;
                textBoxList.ScrollToEnd();
            });

            try
            {
                await Task.Run(() => CleanFolder(folderPath, progress));

                textBoxList.AppendText("Mission complete!");
                textBoxList.CaretIndex = textBoxList.Text.Length;
                textBoxList.ScrollToEnd();
            }
            catch
            {
                System.Windows.MessageBox.Show("Error!");
            }
            finally
            {
                buttonOpen.IsEnabled = true;
            }
        }

        private void CleanFolder(string path, IProgress<string> progress)
        {
            var filePaths = Directory.EnumerateFiles(path, "*.*", System.IO.SearchOption.AllDirectories);
            foreach (var filePath in filePaths)
            {
                var matchResult = Regex.Match(filePath, matchPattern);
                if (matchResult.Success)
                {
                    File.Delete(filePath);
                    progress.Report(filePath);
                }
            }
        }
    }
}
命名空间文件夹清理器
{
公共部分类主窗口:窗口
{
字符串折叠路径;
字符串匹配模式;
私有无效按钮打开单击(对象发送者,路由目标)
{
FolderBrowserDialog fbd=新建FolderBrowserDialog(){Description=“选择文件夹”};
if(fbd.ShowDialog()==System.Windows.Forms.DialogResult.OK)
{
folderPath=fbd.SelectedPath;
Text=folderPath;
buttonClean.IsEnabled=true;
textBoxList.Text=“文件夹路径:”+folderPath+“\n”;
}
}
专用异步无效按钮清除单击(对象发送方,路由目标)
{
matchPattern=textBoxPattern.Text;
buttonOpen.IsEnabled=false;
buttonClean.IsEnabled=false;
进度=新进度(消息=>
{
textBoxList.AppendText(“文件已删除:“+msg+”\n”);
textBoxList.CaretIndex=textBoxList.Text.Length;
textBoxList.ScrollToEnd();
});
尝试
{
等待任务。运行(()=>CleanFolder(folderPath,progress));
textBoxList.AppendText(“任务完成!”);
textBoxList.CaretIndex=textBoxList.Text.Length;
textBoxList.ScrollToEnd();
}
抓住
{
System.Windows.MessageBox.Show(“错误!”);
}
最后
{
buttonOpen.IsEnabled=true;
}
}
专用文件夹(字符串路径,IProgress进度)
{
var filepath=Directory.EnumerateFiles(路径“***”,System.IO.SearchOption.AllDirectories);
foreach(filePath中的var filePath)
{
var matchResult=Regex.Match(文件路径,匹配模式);
if(matchResult.Success)
{
File.Delete(文件路径);
进度报告(文件路径);
}
}
}
}
}

无法从其他线程控制GUI

但我认为,真正的问题是将字符串和输出连接到文本框是一种非常低效的操作

在您的情况下,最好在单行中显示删除进度或使用进度条

这是我对你问题的解决方案(我改变了两种方法):


我希望这会有所帮助。

GUI无法从其他线程控制

但我认为,真正的问题是将字符串和输出连接到文本框是一种非常低效的操作

在您的情况下,最好在单行中显示删除进度或使用进度条

这是我对你问题的解决方案(我改变了两种方法):


我希望这会有所帮助。

谢谢大家。简而言之,多亏了C#6.0这本书

我已经找到了解决方案,并且对async/await有了更好的理解

首先,不建议使用
Dispatcher.Invoke
,因为.NETFramework4.5,基于任务的异步已成为主导模式(使用async/awit)

其次,使用async/await有几个原则:

  • wait
    之后的表达式必须是
    Task
    Task
    反对

  • 如果对方法使用
    async
    修饰符,则该方法不
    t
    需要手动返回任务
    方法。编译将封装
    方法作为
    Task`对象

  • 如果使用类似
    async Task Foo()
    的方法,则必须在其中使用
    wait
    关键字

  • 如果没有等待的内容,则删除
    async
    修饰符,使用
    returntask.Run(()=>{dosomething})返回
    Task
    对象。现在,您可以在调用
    Foo()
    的方法中使用
    wait Foo()

  • Task Foo()
    无法操作UI,但
    async Task Foo()
    可以


    • 谢谢大家。简而言之,多亏了C#6.0这本书

      我已经找到了解决方案,并且对async/await有了更好的理解

      首先,不建议使用
      Dispatcher.Invoke
      ,因为.NETFramework4.5,基于任务的异步已成为主导模式(使用async/awit)

      其次,使用async/await有几个原则:

      • wait
        之后的表达式必须是
        Task
        Task
        反对

      • 如果对方法使用
        async
        修饰符,则该方法不
        t
        需要手动返回任务
        方法。编译将封装
        方法作为
        Task`对象

      • 如果使用类似
        async Task Foo()
        的方法,则必须在其中使用
        wait
        关键字

      • 如果没有等待的内容,则删除
        async
        修饰符,使用
        returntask.Run(()=>{dosomething})返回
        Task
        对象。现在,您可以在调用
        Foo()
        的方法中使用
        wait Foo()

      • Task Foo()
        无法操作UI,但
        async Task Foo()
        可以


      此处不需要显示整个主窗口代码。您可以删除不相关的代码部分,如构造函数或按钮打开单击处理程序,以使您的问题更容易理解
          private async void ButtonClean_Click(object sender, RoutedEventArgs e)
          {
              matchPattern = textBoxPattern.Text;
              buttonOpen.IsEnabled = false;
              buttonClean.IsEnabled = false;
      
              await Task.Run(() => CleanFolder(folderPath));
      
              textBoxList.Text += "Mission complete!";
              buttonOpen.IsEnabled = true;
          }
      
          private void CleanFolder(string path)
          {
              var filePaths = Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories);
              foreach (var filePath in filePaths)
              {
                  var matchResult = Regex.Match(filePath, matchPattern);
                  if (matchResult.Success)
                  {
                      File.Delete(filePath);
                      System.Windows.Application.Current.Dispatcher.Invoke(delegate
                      {
                          // this working fast
                          textBoxList.Text  = "File deleted: " + filePath + "\n";
                          // this working slow and slower over time
                        //textBoxList.Text += "File deleted: " + filePath + "\n";
                          textBoxList.ScrollToEnd();
                      });
                  }
              }
          }