.NET一个线程更新另一个线程的UI(两种实现方法)

1.多线程调用无参函数

复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace 多线程
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("主线程开始");
            Thread t = new Thread(new ThreadStart(ShowTime));//注意ThreadStart委托的定义形式
            t.Start();//线程开始,控制权返回Main线程
            Console.WriteLine("主线程继续执行");
            //while (t.IsAlive == true) ;
            Thread.Sleep(1000);
            t.Abort();
            t.Join();//阻塞Main线程,直到t终止
            Console.WriteLine("--------------");
            Console.ReadKey();
        }
        static void ShowTime()
        {
            while (true)
            {
                Console.WriteLine(DateTime.Now.ToString());               
            }
        }
    }
}
复制代码

注意ThreadStart委托的定义如下:

可见其对传递进来的函数要求是:返回值void,无参数。

2.多线程调用带参函数(两种方法)

复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace 多线程2_带参数
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Main线程开始");
            Thread t = new Thread(new ParameterizedThreadStart(DoSomething));//注意ParameterizedThreadStart委托的定义形式
            t.Start(new string[]{"Hello","World"});
            Console.WriteLine("Main线程继续执行");

            Thread.Sleep(1000);
            t.Abort();
            t.Join();//阻塞Main线程,直到t终止
            Console.ReadKey();
        }
        static void DoSomething(object  s)
        {
            string[] strs = s as string[];
            while (true)
            {
                Console.WriteLine("{0}--{1}",strs[0],strs[1]);
            }
        }
    }
}
复制代码

注意ParameterizedThreadStart委托的定义如下:

可见其对传入函数的要求是:返回值void,参数个数1,参数类型object

复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace 多线程2_带参数2
{   
    class Program
    {
        static void Main(string[] args)
        {
            Guest guest = new Guest() 
            {
             Name="Hello", Age=99
            };
            Thread t = new Thread(new ThreadStart(guest.DoSomething));//注意ThreadStart委托的定义形式
            t.Start();

            Thread.Sleep(1000);
            t.Abort();
            t.Join();//阻塞Main线程,直到t终止
            Console.ReadKey();
        }
    }
    //
    class Guest
    {
        public string Name { get; set; }
        public int Age { get; set; }

        public void DoSomething()
        {
            while (true)
            {
                Console.WriteLine("{0}--{1}", Name, Age);
            }
        }
    }
}
复制代码

这个还是使用ThreadStart委托,对方法进行了一个封装。

两种方法,可随意选择,第一种貌似简洁一点。

3.线程同步

线程同步的方法有很多很多种volatile、Lock、InterLock、Monitor、Mutex、ReadWriteLock...

这里用lock说明问题:在哪里同步,用什么同步,同步谁?

首先感受下不同步会出现的问题:

代码就是下面的代码去掉lock块。

复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace 多线程3_同步2
{
    class Program
    {
        static object obj = new object();//同步用

        static int balance = 500;

        static void Main(string[] args)
        {
            Thread t1 = new Thread(new ThreadStart(Credit));
            t1.Start();

            Thread t2 = new Thread(new ThreadStart(Debit));
            t2.Start();

            Console.ReadKey();
        }

        static void Credit()
        {
            for (int i = 0; i < 15; i++)
            {
                lock (obj)
                {
                    balance += 100;
                    Console.WriteLine("After crediting,balance is {0}", balance);
                }
            }
        }
        static void Debit()
        {
            for (int i = 0; i < 15; i++)
            {
                lock (obj)
                {
                    balance -= 100;
                    Console.WriteLine("After debiting,balance is {0}", balance);
                }
            }
        }
    }
}
复制代码

小结:多线程调用函数就是这样。在Winform中,控件绑定到特定的线程,从另一个线程更新控件,不应该直接调用该控件的成员,这个非常有用,下篇博文讲。

Winform中的控件是绑定到特定的线程的(一般是主线程),这意味着从另一个线程更新主线程的控件不能直接调用该控件的成员。

控件绑定到特定的线程这个概念如下:

为了从另一个线程更新主线程的Windows Form控件,可用的方法有:

首先用一个简单的程序来示例,这个程序的功能是:在Winfrom窗体上,通过多线程用label显示时间。给出下面的两种实现方式

1.结合使用特定控件的如下成员

InvokeRequired属性:返回一个bool值,指示调用者在不同的线程上调用控件时是否必须使用Invoke()方法。如果主调线程不是创建该控件的线程,或者还没有为控件创建窗口句柄,则返回true。

Invoke()方法:在拥有控件的底层窗口句柄的线程上执行委托。

BeginInvoke()方法:异步调用Invoke()方法。

EndInvoke()方法:获取BeginInvoke()方法启动的异步操作返回值。

复制代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace 一个线程更新另一个线程UI2
{
    /// <summary>
    /// DebugLZQ
    /// http://www.cnblogs.com/DebugLZQ
    /// </summary>
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void UpdateLabel(Control ctrl, string s)
        {
            ctrl.Text = s;
        }
        private delegate void UpdateLabelDelegate(Control ctrl, string s);

        private void PrintTime()
        {
            if (label1.InvokeRequired == true)
            {
                UpdateLabelDelegate uld = new UpdateLabelDelegate(UpdateLabel);
                while(true)
                {
                    label1.Invoke(uld, new object[] { label1, DateTime.Now.ToString() });
                }
            }
            else
            {
                while (true)
                {
                    label1.Text = DateTime.Now.ToString();
                }
            }
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //PrintTime();//错误的单线程调用

            Thread t = new Thread(new ThreadStart(PrintTime));
            t.Start();
        }
    }
}
复制代码

比较和BackgroundWorker控件方式的异同点。

2.使用BackgroundWorker控件。

复制代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace 一个线程更新另一个线程UI
{
    /// <summary>
    /// DebugLZQ
    /// http://www.cnblogs.com/DebugLZQ
    /// </summary>
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void UpdateLabel(Control ctrl, string s)
        {
            ctrl.Text = s;
        }

        private delegate void UpdateLabelDelegate(Control ctrl, string s);

        private void PrintTime()
        {
            if (label1.InvokeRequired == true)
            {
                UpdateLabelDelegate uld = new UpdateLabelDelegate(UpdateLabel);
                while (true)
                {
                    label1.Invoke(uld, new object[] { label1, DateTime.Now.ToString() });
                }
            }
            else
            {
                while (true)
                {
                    label1.Text = DateTime.Now.ToString();
                }
            }
        }

        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            PrintTime();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            backgroundWorker1.RunWorkerAsync();
        }
    }
}
复制代码

程序的运行结果如下:

更新另一个线程的进度条示例(第一种方法实现)

DebugLZQ觉得第一种方法要更直观一点,或是更容易理解一点。下面再用第一种方法来做一个Demo:输入一个数,多线程计算其和值更新界面上的Label,并用进度条显示计算的进度。实际上就是,更新另一个线程的两个UI控件。

复制代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace 一个线程更新另一个线程的UI3
{
    /// <summary>
    /// DebugLZQ
    /// http://www.cnblogs.com/DebugLZQ
    /// </summary>
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }


        private static long result = 0;
        

        //更新Label
        private void UpdateLabel(Control ctrl, string s)
        {
            ctrl.Text = s;
        }

        private delegate void UpdateLabelDelegate(Control ctrl, string s);

        //更新ProgressBar
        private void UpdateProgressBar(ProgressBar ctrl, int n)
        {
            ctrl.Value = n;
        }

        private delegate void UpdateProgressBarDelegate(ProgressBar ctrl, int n);


        private void Sum(object o)
        {
            result = 0;
            
            long num = Convert.ToInt64(o);

            UpdateProgressBarDelegate upd = new UpdateProgressBarDelegate(UpdateProgressBar);

            for (long i = 1; i <= num; i++)
            {
                result += i;
                //更新ProcessBar1
                if (i % 10000 == 0)//这个数值要选的合适,太小程序会卡死
                {
                    if (progressBar1.InvokeRequired == true)
                    {
                        progressBar1.Invoke(upd, new object[] { progressBar1, Convert.ToInt32((100 * i) / num) });//若是(i/num)*100,为什么进度条会卡滞?
                    }
                    else
                    {
                        progressBar1.Value = Convert.ToInt32(i / num * 100);
                    }                    
                }
                
            }            

            //更新lblResult
            if (lblResult.InvokeRequired == true)
            {
                UpdateLabelDelegate uld = new UpdateLabelDelegate(UpdateLabel);
                lblResult.Invoke(uld, new object[] { lblResult, result.ToString() });
            }
            else
            {
                lblResult.Text = result.ToString();
            }            

        }
        
        private void btnStart_Click(object sender, EventArgs e)
        {
            Thread t = new Thread(new ParameterizedThreadStart(Sum));
            t.Start(txtNum.Text);
        }
   

    }
}
复制代码

程序的运行结果如下:

用BackgroundWorker控件可以实现相同的功能,个人觉得这样更容易理解~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值