使用await运行耗时的任务时,UI被阻止

时间:2017-01-08 11:59:36

标签: c# wpf multithreading

我想构建一个文件夹清理程序。预计会将已删除的文件实时报告给TextBox控件。所以我在我的按钮点击事件中使用了await 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);
                }
            }
        }
    }
}

2 个答案:

答案 0 :(得分:2)

GUI不能从另一个线程控制。

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

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

以下是我的问题解决方案(我改变了2种方法):

    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();
                });
            }
        }
    }

我希望这会有所帮助。

答案 1 :(得分:0)

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

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

首先,自.Net Framework 4.5以来,不建议使用Dispatcher.Invoke,基于任务的异步已成为主导模式(使用async / awit)。

其次,使用async / await有一些原则:

  • await后的表达必须是TaskTask<TResult> 对象

  • 如果您对方法使用async修饰符,则方法为t need to return a任务method manually. The compile will wrap the method as a任务对象。

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

  • 如果没有任何等待,请删除async修饰符,使用Task返回return Task.Run(() => { Do Something });个对象。现在,您可以在调用await Foo()

  • 的方法中使用Foo()
  • Task Foo()无法操作用户界面,但async Task Foo()可以。