C#多线程,基础知识很重要

本文通过介绍C#多线程的用法(基础玩法),附加介绍一下WinForm里边跨线程访问UI的方法

如图,就是这么一个简单的界面,每个按钮下面一个方法,分别设置文本框里边的内容,那么,开始吧!

先介绍一下WinForm的线程模型:WinForm 是通过调用Windows API 的GetMessage Or PeekMeeage来处理其他线程发送过来的消息,这些消息存储在系统的一个消息队列中,创建主界面的线程就是主线程(UI线程),UI线程负责消费该消息队列中的消息。

WinForm框架中有一个ISynchronizeInvoke接口,所有的UI元素都继承自该接口,接口中的InvokeRequired属性表示了当前线程是否是创建它的线程,接口中的BeginInvoke or Invoke 负责将消息发送到消息队列,这样UI线程就能够正确的访问它了。

那么,首先看代码片段一:里边就实现了将设置文本框内容的消息发送到了消息队列

private void SetMessage(string message)
        {
            if (this.txtMsg.InvokeRequired)
            {
                //BeginInvoke or Invoke 负责将消息发送到消息队列
                this.txtMsg.BeginInvoke(new Action<string>((msg) =>
                {
                    this.txtMsg.Text = msg;
                }), message);
            }
            else
            {
                this.txtMsg.Text = message;
            }

        }

代码片段二Thread:Thread可能是用的最多的了,也是最早的框架里边就有的。这种写法很简单,也很方便,需要提一下的就是IsBackground属性,IsBackground=true表示为后台线程,应用程序退出,哪怕任务没有执行完,也会退出;IsBackground=false表示为后台线程,默认为false,应用程序退出,只要任务还没有执行完,进程就不会结束。

private void btnThread_Click(object sender, EventArgs e)
        {
            Thread thread = new Thread(() =>
            {
                SetMessage("Thread 跨线程访问UI");
            });
            //IsBackground=true表示为后台线程 应用程序退出 哪怕任务没有执行完 也会退出
            //IsBackground=false表示为后台线程 默认为false 应用程序退出 只要任务还没有执行完 进程就不会结束
            thread.IsBackground = true;
            thread.Start();
        }

代码片段三ThreadPool:ThreadPool是微软为了避免开发人员,无节制的使用线程,而提供的一个线程管理类

private void btnThreadPool_Click(object sender, EventArgs e)
        {
            //线程池 是微软为了避免开发人员 无节制的使用线程 而提供的一个线程管理类
            ThreadPool.QueueUserWorkItem((obj) =>
            {
                SetMessage("ThreadPool 跨线程访问UI");
            }, null);
        }

代码片段四Task:Task有很多的优势,也是后面高版本才推出来的,推荐使用。Task与Thread的区别就是:Task使用的是线程池中的线程,Task较之线程池的优势是:

1.Task支持取消,完成,失败通知等交互性操作

2.Task支持线程执行的先后次序

private void btnTask_Click(object sender, EventArgs e)
        {
            /* Task与Thread的区别就是:Task使用的是线程池中的线程
             * Task较之线程池的优势是:
             * 1.Task支持取消,完成,失败通知等交互性操作
             * 2.Task支持线程执行的先后次序*/
            Task.Factory.StartNew(() =>
            {
                SetMessage("Task 跨线程访问UI");
            });

            Task task = new Task(()=> {
                SetMessage("Task 跨线程访问UI");
            });
            task.Start();
        }

代码片段五BackgroundWorker:BackgroundWorker内部是通过线程池实现的,通过事件提供了跨线程访问UI的能力,这个做CS开发,是用的最多的,说白了,太好用。

//BackgroundWorker 内部是通过线程池实现的
        //BackgroundWorker 通过事件提供了跨线程访问UI的能力
        BackgroundWorker _bgw = new BackgroundWorker();

        private void btnBackgroundWorker_Click(object sender, EventArgs e)
        {
            _bgw.WorkerReportsProgress = true;
            _bgw.WorkerSupportsCancellation = true;
            _bgw.DoWork += _bgw_DoWork; ;
            _bgw.ProgressChanged += _bgw_ProgressChanged;
            _bgw.RunWorkerCompleted += _bgw_RunWorkerCompleted;

            if (!_bgw.IsBusy)
            {
                _bgw.RunWorkerAsync();
            }
        }

        private void _bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            this.txtMsg.Text = "BackgroundWorker 跨线程访问UI";//注意这里是直接访问UI
        }

        private void _bgw_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            this.txtMsg.Text = e.UserState.ToString();//注意这里是直接访问UI
        }

        private void _bgw_DoWork(object sender, DoWorkEventArgs e)
        {
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(1000);
                _bgw.ReportProgress(i, $"{i}秒");
            }
        }

代码片段六SynchronizationContext:SynchronizationContext同步上下文在通讯中充当传输者的角色,实现功能就是一个线程和另外一个线程的通讯,这个在跨线程一次性要更新很多的UI控件的时候,非常的适用。

//SynchronizationContext 在通讯中充当传输者的角色,实现功能就是一个线程和另外一个线程的通讯
        SynchronizationContext _syncContext;

        private void btnSynchronizationContext_Click(object sender, EventArgs e)
        {
            _syncContext = SynchronizationContext.Current;
            Thread thread = new Thread(() =>
            {
                if (_syncContext != null)
                {
                    SendOrPostCallback callBack = (obj) =>
                    {
                        //在某个子线程里 一次性要更新很多UI控件的时候 用这个方法 很nice
                        this.txtMsg.Text = "SynchronizationContext 跨线程访问UI";
                    };
                    _syncContext.Post(callBack, null);//异步
                    //_syncContext.Send(callBack, null);//同步
                }
            });
            thread.IsBackground = true;
            thread.Start();
        }

合并之后的代码为:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace ThreadChapter
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void btnThread_Click(object sender, EventArgs e)
        {
            Thread thread = new Thread(() =>
            {
                SetMessage("Thread 跨线程访问UI");
            });
            //IsBackground=true表示为后台线程 应用程序退出 哪怕任务没有执行完 也会退出
            //IsBackground=false表示为后台线程 默认为false 应用程序退出 只要任务还没有执行完 进程就不会结束
            thread.IsBackground = true;
            thread.Start();
        }

        /*
         * WinForm 是通过调用Window API 的GetMessage Or PeekMeeage来处理其他线程发送过来的消息,
         * 这些消息存储在系统的一个消息队列中,创建主界面的线程就是主线程(UI线程),UI线程负责处理该消息队列
         */

        private void SetMessage(string message)
        {
            if (this.txtMsg.InvokeRequired)
            {
                //BeginInvoke or Invoke 负责将消息发送到消息队列
                this.txtMsg.BeginInvoke(new Action<string>((msg) =>
                {
                    this.txtMsg.Text = msg;
                }), message);
            }
            else
            {
                this.txtMsg.Text = message;
            }

        }

        private void btnThreadPool_Click(object sender, EventArgs e)
        {
            //线程池 是微软为了避免开发人员 无节制的使用线程 而提供的一个线程管理类
            ThreadPool.QueueUserWorkItem((obj) =>
            {
                SetMessage("ThreadPool 跨线程访问UI");
            }, null);
        }

        private void btnTask_Click(object sender, EventArgs e)
        {
            /* Task与Thread的区别就是:Task使用的是线程池中的线程
             * Task较之线程池的优势是:
             * 1.Task支持取消,完成,失败通知等交互性操作
             * 2.Task支持线程执行的先后次序*/
            Task.Factory.StartNew(() =>
            {
                SetMessage("Task 跨线程访问UI");
            });
        }


        //BackgroundWorker 内部是通过线程池实现的
        //BackgroundWorker 通过事件提供了跨线程访问UI的能力
        BackgroundWorker _bgw = new BackgroundWorker();

        private void btnBackgroundWorker_Click(object sender, EventArgs e)
        {
            _bgw.WorkerReportsProgress = true;
            _bgw.WorkerSupportsCancellation = true;
            _bgw.DoWork += _bgw_DoWork; ;
            _bgw.ProgressChanged += _bgw_ProgressChanged;
            _bgw.RunWorkerCompleted += _bgw_RunWorkerCompleted;

            if (!_bgw.IsBusy)
            {
                _bgw.RunWorkerAsync();
            }
        }

        private void _bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            this.txtMsg.Text = "BackgroundWorker 跨线程访问UI";//注意这里是直接访问UI
        }

        private void _bgw_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            this.txtMsg.Text = e.UserState.ToString();//注意这里是直接访问UI
        }

        private void _bgw_DoWork(object sender, DoWorkEventArgs e)
        {
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(1000);
                _bgw.ReportProgress(i, $"{i}秒");
            }
        }

        //SynchronizationContext 在通讯中充当传输者的角色,实现功能就是一个线程和另外一个线程的通讯
        SynchronizationContext _syncContext;

        private void btnSynchronizationContext_Click(object sender, EventArgs e)
        {
            _syncContext = SynchronizationContext.Current;
            Thread thread = new Thread(() =>
            {
                if (_syncContext != null)
                {
                    SendOrPostCallback callBack = (obj) =>
                    {
                        //在某个子线程里 一次性要更新很多UI控件的时候 用这个方法 很nice
                        this.txtMsg.Text = "SynchronizationContext 跨线程访问UI";
                    };
                    _syncContext.Post(callBack, null);//异步
                    //_syncContext.Send(callBack, null);//同步
                }
            });
            thread.IsBackground = true;
            thread.Start();
        }
    }
}

猜你喜欢

转载自www.cnblogs.com/dwBurning/p/NetThread.html