WinForm(七)在新线程中更新UI

在WinForm项目中,很多时候会映遇上多线程一起工作的情况,因为当前UI的更新显示,是在主线程中,一但主线程被长时运算占据后,UI就会被卡信,出现假死现像。那么就需要起一个新线程做长时运算工作,把进度或数据同步回UI线程。

以一个医保上传数据为例,功能是同步药品,器械,诊疗项目,同步完后进行验证核对。

注:为了看得清晰,各个关键控件我没有重命名

定义一个list来充当步骤和需要时间。

static List<Item> list;
private void Form1_Load(object sender, EventArgs e)
{
    list = new List<Item>
    {
        new Item{ Name="正在上传诊疗项目",Time=8 },
        new Item{ Name="正在上传器材",Time=12 },
        new Item{ Name="正在上传药品",Time=20 },
        new Item{ Name="正在核对",Time=24 },
    };
}

第一版:用Task.Run来启动一个新的线程,中的for循环是为了表示一个进度的...的切换,如果换成一个gif图标更佳,foreach循环是完成list中各项任务的。如果运行,你会发现,窗体上是空白的,18行的给messageLabel.Text不起作用。

private void button1_Click(object sender, EventArgs e)
{
    Task.Run(() =>
    {
        foreach (var item in list)
        {
            var dotString = "";
            for (var i = 0; i < item.Time; i++)
            {
                if (i % 6 == 0)
                {
                    dotString = ".";
                }
                else
                {
                    dotString += ".";
                }
                messageLabel.Text = $"{item.Name}{dotString}";
                SpinWait.SpinUntil(() => false, 300);
            }
        }
        MessageBox.Show("完成医保所有数据同步");
    });
}

第二版:为了更新UI线程,在新线程中用this.Invoke来更新UI上控件的值,如18行。这回没有什么问题了,UI也能即时更新,看上去很完美。

private void button2_Click(object sender, EventArgs e)
{
    Task.Run(() =>
    {
        foreach (var item in list)
        {
            var dotString = "";
            for (var i = 0; i < item.Time; i++)
            {
                if (i % 6 == 0)
                {
                    dotString = ".";
                }
                else
                {
                    dotString += ".";
                }
                this.Invoke(() =>
                {
                    messageLabel.Text = $"{item.Name}{dotString}";
                });
                SpinWait.SpinUntil(() => false, 300);
            }
        }
        MessageBox.Show("完成医保所有数据同步");
    });
}

其实这背后是有异常的(有可能会在vs中报出来),因为当你关闭窗体时,18行的this已经不存在了,但访问this.Invoke在新的线程中,新线程本身并没有关掉,这时就会报找不到实例,有可能你运行起来并不会发现异常,这是因为主线程关闭后,所有创建的子线程都会关闭的,为了验证,你可以5行前面加一个try,在catch中把异常内容写在一个日志文件中。

我的结果是:

Cannot access a disposed object.
Object name: 'Form1'.

第三版:发现问题,那就解决吧(有可能你觉得无所谓,窗体都关闭了,没报异常,看不出来。确实,如果你觉得无所谓,那就无所谓,这里只是来说明技术点的,不是来说服想法的),通过一个mark标志,在关闭窗体时,拦截一下,把子线程关闭,然后再把主窗体关闭,这样就没有问题了,自己起的线程,自己要关掉。

static bool mark = true;
private void startButton_Click(object sender, EventArgs e)
{
    Task.Run(() =>
    {
        foreach (var item in list)
        {
            if (mark == false)
            {
                break;
            }
            var dotString = "";
            for (var i = 0; i < item.Time; i++)
            {
                if (mark == false)
                {
                    break;
                }
                if (i % 6 == 0)
                {
                    dotString = ".";
                }
                else
                {
                    dotString += ".";
                }
                this.Invoke(() =>
                {
                    messageLabel.Text = $"{item.Name}{dotString}";
                });
                SpinWait.SpinUntil(() => false, 300);
            }
        }
        if (mark)
        {
            mark = false;
            MessageBox.Show("完成医保所有数据同步");
        }
        else
        {
            this.Invoke(() =>
            {
                this.Close();
            });
        }
    });
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
    if (mark)
    {
        e.Cancel = true;
        mark = false;
    }
}

线程是个大话题,这只是简单的线程协同,两个线程没有深度协同。通过这个例子,三个版本,一步一步来满足功能,提升健壮性和安全性,我们们可以得出,做了一个功能,是需要下功夫的,不但要知其然,还要知其所以然。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
WinForm程序UI界面是由主线程(也称UI线程)负责更新的,其他线程不能直接修改UI界面数据,否则会抛出异常。如果需要线程更新UI界面数据,可以使用以下几种方法: 1. 使用Control.Invoke方法:该方法可以将UI线程委托一个方法来执行,从而实现线程更新UI界面数据。例如: ``` private void UpdateUI(string data) { if (this.InvokeRequired) { this.Invoke(new Action<string>(UpdateUI), data); } else { label1.Text = data; } } ``` 2. 使用Control.BeginInvoke方法:该方法与Invoke类似,但是它是异步的。它会立即返回,而不是等待委托方法执行完毕。例如: ``` private void UpdateUI(string data) { if (this.InvokeRequired) { this.BeginInvoke(new Action<string>(UpdateUI), data); } else { label1.Text = data; } } ``` 3. 使用SynchronizationContext类:该类可以在不同线程之间传递上下文信息,从而实现线程更新UI界面数据。例如: ``` private SynchronizationContext _syncContext; public Form1() { InitializeComponent(); _syncContext = SynchronizationContext.Current; } private void UpdateUI(string data) { _syncContext.Post(new SendOrPostCallback((obj) => { label1.Text = obj.ToString(); }), data); } ``` 以上三种方法都可以实现线程更新UI界面数据,根据实际情况和个人习惯选择即可。需要注意的是,线程更新UI界面数据是一个比较耗时的操作,如果频繁调用会对程序的性能产生影响。因此,建议仅在必要时使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值