【C#】解决Winform/WPF卡界面的问题
  BYiSzLc5xwC8 2023年11月02日 48 0

今天来给大家讲解一下如何解决C#的Winform/WPF应用卡界面的问题。

1.问题复现

我们在用C#编写桌面软件的时候经常会用到实时更新界面信息的功能,比如这样

private void button1_Click(object sender, EventArgs e)
{
    for (int i = 0; i < 200; i++)
    {
        Thread.Sleep(10);
        label1.Text = $"{DateTime.Now.ToString("yyyy-MM-dd:mm:HH:ss.fff")} i的值为 {i}";
    }
}

此时,界面表现是这样的:

【C#】解决Winform/WPF卡界面的问题_控件

可以看到,代码一旦开始执行,不仅数据不会更新,界面还会拖拽不动,这是因为在循环期间,UI线程被阻塞,无法更新用户界面,并且程序将在等待循环完成时变得不可响应。

2.解决方案

按照平常的想法,既然是UI线程被阻塞,那么利用C#异步编程技术,使用另一个线程去操作UI不就解决了吗?说干就干,接下来修改代码:

private async void button1_Click(object sender, EventArgs e)
{
    await Task.Run(() =>
    {
        for (int i = 0; i < 200; i++)
        {
            Thread.Sleep(10);
            label1.Text = $"{DateTime.Now.ToString("yyyy-MM-dd:mm:HH:ss.fff")} i的值为 {i}";
        }
    });
}

再次尝试运行看看,不好,抛出异常了:

【C#】解决Winform/WPF卡界面的问题_应用程序_02

提示不是从创建控件的线程访问控件,这是为什么呢?原来我们要赋值的控件label1是UI线程创建的,而我们用Task.Run()相当于新建了一个线程,用新线程去调用UI线程创建的控件,就会提示这个错误,那么现在该怎么办呢?别慌,我们可以用委托的方法,再次修改代码点击运行:

private void button1_Click(object sender, EventArgs e)
{
    Task.Run(() =>
    {
        for (int i = 0; i < 200; i++)
        {
            Thread.Sleep(10);
            this.BeginInvoke(new Action(() => label1.Text = $"{DateTime.Now.ToString("yyyy-MM-dd:mm:HH:ss.fff")} i的值为 {i}"));
        }
    });
}

【C#】解决Winform/WPF卡界面的问题_应用程序_03

我们可以看到,不仅数据在实时更新,且随意拖动界面都不会影响数据更新。

3.原理说明

接下来,我们详细阐述一下原理:

BeginInvoke 方法是一个异步方法,它允许通过委托在控件的 UI 线程上异步执行代码,new Action() 是一个简单的匿名方法,将会在UI线程上执行,当UI线程空闲时,该方法将会被异步调用。

使用 BeginInvoke 的好处是,即使在执行长时间运行的操作时,也不会阻塞 UI 线程。这意味着用户仍然可以与应用程序进行交互,并且应用程序的响应性不会受到影响。

当调用 BeginInvoke 时,系统会将任务添加到 UI 线程的消息队列中。一旦消息队列中没有正在执行的任务,UI 线程就会从消息队列中获取下一个任务并执行它。这样,UI 线程就可以在需要执行的任务之间快速切换,从而保持应用程序的响应性和流畅性。

好了,今天的分享就到这里,祝大家生活愉快。

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
  mcbWRrRPlhs5   2023年11月30日   28   0   0 访问令牌API应用程序