当前位置 : 首页 » 博文聚焦 » 正文

2017.10.20 C#跨线程操作控件的线程安全方法

分类 : 博文聚焦 | 发布时间 : 2017-10-20 22:32:30 | 浏览 : 165

C#跨线程操作控件的线程安全方法

在C#中,经常用到这样一个场景,Windows Form程序启动一个工作者线程执行一部分工作,这样做是为了避免速度慢的工作如果直接调用会使得主Form停止响应一段时间。
既然启动了线程,就避免不了线程之间数据传递的事情,相信你有很多种办法能解决,总之注意同步和互斥操作就好。我想说的是,工作线程处理中可能想操作某个主线程的Windows Form的Control,比如按钮,ListView等等更新工作状态之类,直接控制是不行的,不能够跨线程操作另一个线程创建的Windows Form控件。要使用委托去调用。


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

namespace JPGCompact
{
    public partial class MainForm : Form
    {
        // 定义委托
        private delegate void DelegateWriteResult(string file, bool result);

        // 与定义的委托签名相同的函数,操作主线程控件
        private void WriteResult(string fileName, bool result)
        {
            if (result)
            {
                ListViewItem thisListItem = new ListViewItem();
                thisListItem.ForeColor = Color.White;
                thisListItem.BackColor = Color.DarkGreen;
                thisListItem.SubItems[0].Text = fileName;
                thisListItem.SubItems.Add("成功");
                lvResultList.Items.Add(thisListItem);
            }
            else
            {
                ListViewItem thisListItem = new ListViewItem();
                thisListItem.ForeColor = Color.White;
                thisListItem.BackColor = Color.Red;
                thisListItem.SubItems[0].Text = fileName;
                thisListItem.SubItems.Add("失败");
                lvResultList.Items.Add(thisListItem);
            }
        }

        // 启动线程
        private void btnStart_Click(object sender, EventArgs e)
        {
            Thread workThread = new Thread(new ThreadStart(CompressAll));
            // 设置为背景线程,主线程一旦推出,该线程也不等待而立即结束
            workThread.IsBackground = true;
            workThread.Start();
        }

        // 线程执行函数
       private void CompressAll()
        {
            // 判断是否需要Invoke,多线程时需要
            if (this.InvokeRequired)
           {
                // 通过委托调用写主线程控件的程序,传递参数放在object数组中
                this.Invoke(new DelegateWriteResult(WriteResult),
                                new object[] { item, true });
           }
            else
            {
                // 如果不需要委托调用,则直接调用
                this.WriteResult(item, true);
            }
        }
    }
}

C# 多线程控制控件实例
该实例功能为“多线程控制UI控件”,线程函数实现自动加1。界面如下:
这里写图片描述

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

namespace treadTest
{    
    //定义委托
    public delegate void ListBoxDelegate(); 
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        //委托处理方法(关联与ListBoxDelegate)
        private void ListBox()
        {
            if (!listBox1.InvokeRequired)//如果在UI主线程操作ListBox,
            {
                listBox1.Items.Add(++CommonData.num);//则直接进行控件操作,“与UI主线程相关联”
                listBox1.SelectedItem = listBox1.Items[listBox1.Items.Count - 1]; 
            }
            else//如果是在另一线程操作ListBox,则启用委托
                listBox1.Invoke(new ListBoxDelegate(listShow));
        }

        //定义对UI主线程控件的操作,“与AddAuto相关联”。
        private void listShow()
        {
            listBox1.Items.Add(CommonData.num);
            listBox1.SelectedItem = listBox1.Items[listBox1.Items.Count - 1]; 
        }
        //定义线程函数
        private void AddAuto()
        {
            while (CommonData.Flag == 0)
            {
                CommonData.num++;
                Thread.Sleep(1000);
                ListBox();//不能直接控制UI上的控件,所以用该方法选择使用委托
            }
        }
        //在click事件中启动多线程
        private void btnStart_Click(object sender, EventArgs e)
        {
            //线程标志置0,表示开启线程
            CommonData.Flag = 0;
            //定义 ThreadStart的委托类型的参数,并使该委托指向线程函数
            ListBoxDelegate mycn = new ListBoxDelegate(AddAuto);
            //实例化线程
            Thread insertTxt = new Thread(new ThreadStart(mycn));
            //启动线程
            insertTxt.Start();      
        }

        private void btnAbort_Click(object sender, EventArgs e)
        {
            CommonData.Flag = 1;
        }
        private void btnCtrlMain_Click(object sender, EventArgs e)
        {
            ListBox();
        }
        private void btnReset_Click(object sender, EventArgs e)
        {
            CommonData.num = 0;
        }
        private void btnClear_Click(object sender, EventArgs e)
        {
            listBox1.Items.Clear();
        }
        private void btnQuit_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }


    }
     //全局变量解决方案
    public class CommonData
    {
        private static int _Flag = 0;
        private static int _num = 0;
        public static int Flag
        {
            get { return _Flag; }
            set { _Flag = value; }
        }
        public static int num
        {
            get { return _num; }
            set { _num = value; }
        }
    }
}

总结:
要使用多线程控制UI控件,必须用委托实现。调用控件的Invoke方法(Invoke方法的参数是一个委托类型的参数)。
实现步骤:

1.声明委托。
2.声明委托处理函数(判断是主线程控制UI控件,还是Invoke(多线程)控制UI控件)。
3.声明一个线程实例,将线程函数的委托传入ThreadStart()。
4.开启该线程。
5.定义该线程函数,欲控制UI控件,则调用第2步的委托处理函数,他将自己判断选择用Invoke。
6.定义Invoke需要调用的函数(如本例的listShow函数)
//***********************************************************************************************************************
在上例中,只是完成了多线程控制主线程控件的功能,如果能手动和自动同时访问全局变量时,就有可能出现线程不同步的问题。以下主要利用lock线程锁来修改解决方案,使线程同步,注意代码带动的地方。

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

namespace treadTest
{    
    //定义委托
    public delegate void ListBoxDelegate(); 
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        //委托处理方法(关联与ListBoxDelegate)
        private void ListBox()
        {
            if (!listBox1.InvokeRequired)//如果在UI主线程操作ListBox,
            {
                listBox1.Items.Add(CommonData.plus());//则直接进行控件操作,“与UI主线程相关联”
                listBox1.SelectedItem = listBox1.Items[listBox1.Items.Count - 1]; 
            }
            else//如果是在另一线程操作ListBox,则启用委托
                listBox1.Invoke(new ListBoxDelegate(listShow));
        }

        //定义对UI主线程控件的操作,“与AddAuto相关联”。
        private void listShow()
        {
            listBox1.Items.Add(CommonData.plus());
            listBox1.SelectedItem = listBox1.Items[listBox1.Items.Count - 1]; 
        }
        //定义线程函数
        private void AddAuto()
        {
            while (CommonData.Flag == 0)
            {
                Thread.Sleep(1000);
                ListBox();//不能直接控制UI上的控件,所以用该方法选择使用委托
            }
        }
        //在click事件中启动多线程
        private void btnStart_Click(object sender, EventArgs e)
        {
            //线程标志置0,表示开启线程
            CommonData.Flag = 0;
            //定义 ThreadStart的委托类型的参数,并使该委托指向线程函数
            ListBoxDelegate mycn = new ListBoxDelegate(AddAuto);
            //实例化线程
            Thread insertTxt = new Thread(new ThreadStart(mycn));
            //启动线程
            insertTxt.Start();      
        }

        private void btnAbort_Click(object sender, EventArgs e)
        {
            CommonData.Flag = 1;
        }
        private void btnCtrlMain_Click(object sender, EventArgs e)
        {
            ListBox();
        }
        private void btnReset_Click(object sender, EventArgs e)
        {
            CommonData.num = 0;
        }
        private void btnClear_Click(object sender, EventArgs e)
        {
            listBox1.Items.Clear();
        }
        private void btnQuit_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }
    }

    //全局变量解决方案
    public class CommonData
    {
        private static int _Flag = 0;
        private static int _num = 0;
        public static int plus()
        {
            lock (new object())
            {
                return _num++;
            }
        }
        public static int Flag
        {
            get { return _Flag; }
            set { _Flag = value; }
        }
        public static int num
        {
            get { return _num; }
            set { _num = value; }
        }
    }
}

相关阅读:

Converting string format to datetime in mm/dd/yyyy

Convert HTML to PDF in .NET

Sorting rows in a data table

Best way to read a large file into a byte array in C#?

Run Command Prompt Commands

How do I use Assert to verify that an exception has been thrown?

Merge two (or more) lists into one, in C# .NET

How to open a web page from my application?

Retrieving Property name from lambda expression

What is the default boolean value in C#?