C#线程安全的那些事

2021年09月15日 阅读数:2
这篇文章主要向大家介绍C#线程安全的那些事,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

仍是上一次,面试的时候提到了C#线程安全的问题,当时回答的记不太清了,大概就是多线程同是调用某一个函数时可能会照成数据发生混乱,运行到最后发现产生的结果或数据并非本身想要的,或是跨线程调用属性或方法,即在一个线程中调用另外一个线程中的数据,程序会提醒异常(固然这种问题的解决方法有好几种,这里不重点介绍)。面试

在这里详细总结了关于线程安全的一些问题,但愿对你们有点帮助,若有错误的地方欢迎指出数组

1.线程安全:安全

若是你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。若是每次运行结果和单线程运行的结果是同样的,并且其余的变量的值也和预期的是同样的,就是线程安全的。
或者说:一个类或者程序所提供的接口对于线程来讲是原子操做或者多个线程之间的切换不会致使该接口的执行结果存在二义性,也就是说咱们不用考虑同步的问题。
线程安全问题都是由全局变量及静态标量引发的。
若每一个线程中对全局变量、静态变量只有读操做,而无写操做,通常来讲,这个全局变量是线程安全的;如有多个线程同时执行写操做,通常都须要考虑线程同步,不然的话就可能影响线程安全。
 
2.线程不安全:

举例 好比一个 ArrayList 类,在添加一个元素的时候,它可能会有两步来完成:1. 在 Items[Size] 的位置存放此元素;2. 增大 Size 的值。多线程

在单线程运行的状况下,若是 Size = 0,添加一个元素后,此元素在位置 0,并且 Size=1; 而若是是在多线程状况下,好比有两个线程,线程 A 先将元素存放在位置 0。可是此时 CPU 调度线程A暂停,线程 B 获得运行的机会。线程B也向此 ArrayList 添加元素,由于此时 Size 仍然等于 0 (注意哦,咱们假设的是添加一个元素是要两个步骤哦,而线程A仅仅完成了步骤1),因此线程B也将元素存放在位置0。而后线程A和线程B都继续运行,都增长 Size 的值。 那好,如今咱们来看看 ArrayList 的状况,元素实际上只有一个,存放在位置 0,而 Size 却等于 2。这就是“线程不安全”了。dom

3.解决线程安全的几种方法:ide

当须要在线程之间数据传递时,要解决线程安全只要注意同步和互斥操做就好。工做线程处理中可能想操做某个主线程的Windows Form的Control,好比按钮,ListView等等更新工做状态之类,直接控制是不行的,不可以跨线程操做另外一个线程建立的Windows Form控件。要使用委托去调用。函数

C#线程安全的那些事_控件
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#线程安全的那些事_控件

C#多线程、跨线程与线程安全的示例详解(三种不一样方法)学习

 

C#线程安全的那些事_控件
using System.Threading;

public static class Extensions

    {
        //控件扩展方法(用于跨线程操做),由于为了线程的安全,防止资源竞争出现死锁或不一致的状态,.NET是不容许进行跨线程访问窗体控件的。

        public static void SafeCall(this Control ctrl, Action callback)
        {
            if (ctrl.InvokeRequired)
            {
                ctrl.Invoke(callback);
            }
            else
            {
                callback();
            }
        }
    }

 

    public partial class Form1 : Form
    {

        public Form1()
        {
            InitializeComponent();
            CheckForIllegalCrossThreadCalls = false;//方法二(禁用异常,不检查跨线程调用的安全问题,能够自由拖动窗体,不过在严格条件下也不可取,数据可能不一致)

 

            //方法三(推荐使用)
            //把你要保护起来的代码做为一个回调,而后任何须要保护一些代码的地方均可以这样调用
            ThreadPool.QueueUserWorkItem(h =>
            {
                int i = 0;
                while (true)
                {
                    //若是没有SafeCall方法,将出现“线程间操做无效: 从不是建立控件“textBox1”的线程访问它。”的错误


                    ////匿名委托

                    //textBox1.SafeCall(delegate()

                    //{

                    //    textBox1.Text = (i++).ToString();

                    //});
                    //Lambda表达式
                    textBox1.SafeCall(() =>
                    {
                        textBox1.Text = (i++).ToString();
                    });

                    //Thread.Sleep(100);
                }
            });
        }


        //抽奖示例
      public bool flag = true;
        public void choujiang()
        {
            flag = true;
            while (flag)
            {
                Random rnd = new Random();
                textBox1.Text = rnd.Next(1, 100).ToString();
                //Application.DoEvents();//方法一:这样也能够防止UI界面线程的阻塞,不至于被卡死。可是在拖动界面或其余操做的时候,程序会被暂停
            }
        }

        //开始
        private void button1_Click(object sender, EventArgs e)
        {
            //choujiang();//方法一
            new Action(choujiang).BeginInvoke(null, null);//方法二
        }

        //暂停
        private void button2_Click(object sender, EventArgs e)
        {
            flag = false;
        }
    }
C#线程安全的那些事_控件

 

 

 注:本文章属我的学习总结,部份内容参考互联网上的相关文章。 其中若是发现我的总结有不正确的认知或遗漏的地方请评论告知,欢迎交流。ui