WinForm中UI假死的解决方法

1、前言

WinForm中的UI假死其实是个老生常谈的问题了,但最近还是很多同志问该如何解决,今天就来说明一下。测试程序界面如下图所示:一个Button和一个ProgressBar

在这里插入图片描述

2、主线程的阻塞和UI更新问题

先看一个简单的例子:

using System;
using System.Threading;
using System.Windows.Forms;

namespace App
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
        }

        private void btn_Click(object sender, EventArgs e)
        {
            progressBar1.Minimum = 0;
            progressBar1.Maximum = 100;
            progressBar1.Style = ProgressBarStyle.Blocks;
            progressBar1.Step = 1;
            progressBar1.Value = 0;

            int max = progressBar1.Maximum;
            for (int i = 1; i <= max; i++)
            {
                progressBar1.Value = i;
                Thread.Sleep(100);
            }
        }
    }
}

运行上面的代码,你会发现在程序运行期间界面无法拖动,UI处于假死状态。原因其实很简单:循环中的Thread.Sleep(100)阻塞了线程。既然主线程被阻塞了,那我们自然会想到创建一个子线程去更新控件,现在修改一下代码:

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace App
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
        }

        private void btn_Click(object sender, EventArgs e)
        {
            progressBar1.Minimum = 0;
            progressBar1.Maximum = 100;
            progressBar1.Style = ProgressBarStyle.Blocks;
            progressBar1.Step = 1;
            progressBar1.Value = 0;

            Task task = Task.Run(() =>
            {
                int max = progressBar1.Maximum;
                for (int i = 1; i <= max; i++)
                {
                    progressBar1.Value = i;
                    Thread.Sleep(100);
                }
            });
        }
    }
}

运行上面的代码,发现报错:线程间操作无效: 从不是创建控件“progressBar1”的线程访问它

在这里插入图片描述

3、使用Delagate解决界面卡死问题

首先明确一点:UI线程位于主线程,如果想要在子线程里更新UI状态,则必须要将其切换到主线程,然后进行更新操作。UI控件一般会提供Invoke、InvokeRequired,其中InvokeRequired用于判断是否有子线程在更新UI控件,如果有则返回trueInvoke用于将控制权切换到UI线程,代码如下:

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace App
{
    public delegate void UpdateProgressBarDelegate(int num);

    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
        }

        private void btn_Click(object sender, EventArgs e)
        {
            progressBar1.Minimum = 0;
            progressBar1.Maximum = 100;
            progressBar1.Style = ProgressBarStyle.Blocks;
            progressBar1.Step = 1;
            progressBar1.Value = 0;

            Task task = Task.Run(() =>
            {
                UpdateProgressBar();
            });
        }

        private void UpdateProgressBar()
        {
            int max = progressBar1.Maximum;
            for (int i = 1; i <= max; i++)
            {
                UpdateProgressBarCallback(i);
                Thread.Sleep(100);
            }
        }

        private void UpdateProgressBarCallback(int num)
        {
            if (progressBar1.InvokeRequired)
            {
                progressBar1.Invoke(new UpdateProgressBarDelegate(UpdateProgressBarCallback), new object[] { num });
            }
            else
            {
                progressBar1.Value = num;
            }
        }
    }
}

4、使用BackgroundWorker解决界面卡死问题

WinForm中的BackgroundWorker组件也可以很好地解决界面卡死问题,如下图所示:

在这里插入图片描述

BackgroundWorker组件的核心就是DoWorkProgressChangedProgressChanged三个事件,代码如下:

using System;
using System.Threading;
using System.Windows.Forms;

namespace App
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
            backgroundWorker1.WorkerReportsProgress = true;
            backgroundWorker1.WorkerSupportsCancellation = true;
        }

        private void btn_Click(object sender, EventArgs e)
        {
            if (backgroundWorker1.IsBusy)
            {
                return;
            }
            progressBar1.Minimum = 0;
            progressBar1.Maximum = 100;
            progressBar1.Style = ProgressBarStyle.Blocks;
            progressBar1.Step = 1;
            progressBar1.Value = 0;
            backgroundWorker1.RunWorkerAsync();
        }

        private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
        {
            int max = progressBar1.Maximum;
            for (int i = 1; i <= max; i++)
            {
                backgroundWorker1.ReportProgress(i);
                Thread.Sleep(100);
            }
        }

        private void backgroundWorker1_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
        {
            progressBar1.Value = e.ProgressPercentage;
        }

        private void backgroundWorker1_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
        {
            MessageBox.Show("完成");
        }
    }
}

5、使用async、await解决界面卡死问题

上面提到的DelegateBackgroundWorker组件主要是针对UI的更新问题,其实还有一种情况也会造成UI的卡死,那就是某些耗时操作。先来看一段代码:

using System;
using System.Threading;
using System.Windows.Forms;

namespace App
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
        }

        private void btn_Click(object sender, EventArgs e)
        {
            string message = GetMessage();
            MessageBox.Show(message);
        }

        private string GetMessage()
        {
            Thread.Sleep(5000);
            return "Hello World";
        }
    }
}

在上面的代码中,GetMessage方法模拟了一个耗时操作,运行程序,发现UI假死,界面无法拖动,此时就可以考虑使用asyncawait来解决。代码如下:

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace App
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
        }

        private async void btn_Click(object sender, EventArgs e)
        {
            string message = await GetMessageAsync();
            MessageBox.Show(message);
        }

        private async Task<string> GetMessageAsync()
        {
            return await Task.Run(() =>
            {
                Thread.Sleep(5000);
                return "Hello World";
            });
        }
    }
}

6、使用Task解决界面卡死问题

对于一些老项目,比如.NET 4.0版本下的代码,开发者无法使用asyncawait,此时就可以考虑使用Task来实现。代码如下:

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace App
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
        }

        private void btn_Click(object sender, EventArgs e)
        {
            string message = "";
            Task task = Task.Factory.StartNew(() =>
            {
                message = GetMessage();
            })
            .ContinueWith(t =>
            {
                MessageBox.Show(message);
            });
        }

        private string GetMessage()
        {
            Thread.Sleep(5000);
            return "Hello World";
        }
    }
}

7、结语

本文主要介绍了WinForm中的UI假死解决方案。在实际开发中,合理使用多线程可以有效提高界面的人机交互体验。

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值