C# WPF 冻结 UI

C# WPF Freezing UI

该应用程序从 inputField 中获取文本,在其中搜索正则表达式匹配项,并将结果 returns 放入 outputField。把它放在几行代码中,我有这样的东西:

public class Parser {
    public Parser(string _text)
    {
        text = _text;
    }

    private string text { get; set; }

    public string[] find()
    {

        string r1 = "...";
        string r2 = "...";
        string r3 = "...";

        string[] regArray = new string[] { r1, r2, r3 };

        List<string> resL = new List<string>();

        for (int i = 0; i < regArray.Length; i++)
        {
            MatchCollection matchList = Regex.Matches(text, regArray[i]);
            var list = matchList.Cast<Match>().Select(match => match.Value).ToList();
            resL.AddRange(list);
        }

        string[] res = resL.Distinct().ToArray();
        if (res.Length > 0)
            return res;
        return new string[0];
    } }

private async void FindButton_Click(object sender, RoutedEventArgs e) {
    FindButton.Content = "Searching...";
    FindButton.IsEnabled = false;
    await Task.Delay(1);

    try
    {
        string parsed = string.Empty;
        if (string.IsNullOrWhiteSpace(new TextRange(InputField.Document.ContentStart, InputField.Document.ContentEnd).Text)) ;
        {
            OutputField.Document.Blocks.Clear();
            MessageBox.Show("Empty input");
        }
        else
        {
            Parser nOb = new Parser(new TextRange(InputField.Document.ContentStart, InputField.Document.ContentEnd).Text);
            string[] result = nOb.find();

            if (result.Length == 0)
            {
                OutputField.Document.Blocks.Clear();
                MessageBox.Show("Nothing found");
            }
            else
            {
                for (int i = 0; i < result.Length; i++)
                {
                    parsed += result[i] + Environment.NewLine;
                }

                OutputField.Document.Blocks.Clear();
                OutputField.Document.Blocks.Add(new Paragraph(new Run(parsed)));

                MessageBox.Show("Success");
            }
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show("Error: " + ex.Message);
    }

    FindButton.Content = "Default";
    FindButton.IsEnabled = true; 
}

问题是当来自 inputField 的文本非常大并且程序试图找到所有匹配项时 UI 开始冻结。越来越无法最小化程序window,Windows说应用没有响应,问我是否要关闭它。如果我在工作期间不单击该程序,它就可以正常完成。那么是否有可能避免以某种方式冻结并在处理大量输入时最小化应用程序?感谢任何帮助。

这是预期的行为,因为您 运行 默认情况下您的任务在 UI 线程上,只要它没有完成,就不可能进行 UI 操作。

您需要使用 Task.Run 在非 UI 线程上制作代码 运行。但是您可能需要更新一些控件,然后您需要返回到 UI 线程,然后您可以使用 Dispatcher.RunAsync,尽管最好在 WPF 中使用数据绑定。

这些都是非常基本的东西,您应该在 MSDN 上进一步阅读这些主题。

这太可怕了:

for (int i = 0; i < result.Length; i++)
{
    parsed += result[i] + Environment.NewLine;
}

因为字符串是不可变的,这会创建 N 个字符串,每个都比最后一个长,总内存消耗为 O(N2)。而复制浪费的时间也是O(N2).

最好使用String.Join,但是当你需要在循环中进行字符串操作时,使用System.Text.StringBuilder,它会修改其内部缓冲区而不是将其丢弃并为string 必须的每一个操作。

但是,为什么首先要将多行字符串变成单个 运行?可能其中每一个都应该是一个单独的 Paragraph 对象,并让 WPF 容器管理剪辑(并且不必绘制完全在屏幕外的 Paragraph 对象)。但是,如果您这样做,构建一个与 UI 断开连接的内容对象,然后一步将其添加到 UI,这样您就不会以布局引擎 运行 对每个连接重复一遍。

我不得不承认我不明白你的代码的目标是什么,但我会尝试这样的事情:

我简化了你的 Parser class:

    public class Parser 
    {
        const string _r1 = "...";
        private string _text;

        public Parser(string text)
        {
            _text = text;
        }

        public IEnumerable<string> Find()
        {
            return Regex.Matches(_text, _r1)
                .Cast<Match>()
                .Select(match => match.Value)
                .Distinct();
        } 
    }

还有一部分按钮点击代码,使用 StringBuilder:

Parser nOb = new Parser(yourText);
var result = nOb.Find();
string parsed = string.Empty;

if (!result.Any())
{
    // Nothing found
}
else
{
    var stringBuilder = new StringBuilder();
    foreach (var line in result)
    {
        stringBuilder.AppendLine(line);
    }

    parsed = stringBuilder.ToString();

    // ...parsed
}

如果您更好地指定代码的最终目标,我们可以提供更准确的帮助。

尝试在后台线程上执行 find() 方法:

private async void FindButton_Click(object sender, RoutedEventArgs e)
{
    FindButton.Content = "Searching...";
    FindButton.IsEnabled = false;

    try
    {
        if (string.IsNullOrWhiteSpace(new TextRange(InputField.Document.ContentStart, InputField.Document.ContentEnd).Text))
        {
            OutputField.Document.Blocks.Clear();
            MessageBox.Show("Empty input");
        }
        else
        {
            string[] result;
            await Task.Run(() =>
            {
                Parser nOb = new Parser(new TextRange(InputField.Document.ContentStart, InputField.Document.ContentEnd).Text);
                result = nOb.find();
            });

            if (result == null || result.Length == 0)
            {
                OutputField.Document.Blocks.Clear();
                MessageBox.Show("Nothing found");
            }
            else
            {
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < result.Length; i++)
                {
                    sb.AppendLine(result[i]);
                }

                OutputField.Document.Blocks.Clear();
                OutputField.Document.Blocks.Add(new Paragraph(new Run(sb.ToString())));

                MessageBox.Show("Success");
            }
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show("Error: " + ex.Message);
    }

    FindButton.Content = "Default";
    FindButton.IsEnabled = true;
}