C# 学习笔记线程

Posted 不咸不要钱

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C# 学习笔记线程相关的知识,希望对你有一定的参考价值。

C# 学习笔记(9)线程

本文参考博客
C#多线程 https://www.cnblogs.com/dotnet261010/p/6159984.html
C# 线程与进程 https://www.cnblogs.com/craft0625/p/7496682.html
C# 线程同步https://www.cnblogs.com/dotnet261010/p/12335538.html
对于c#中的线程和进程,这几篇文章讲的相当到位了,本文只是为了学习做的摘要。

线程间数据同步

看下面一个例子,有线程t1和t2 都操作变量 Counter 自增一千次,理论上我们最后应该得到 Counter = 2000;

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        private static int Counter = 0;
        private void btnPrint_Click(object sender, EventArgs e)
        {
            Counter = 0;
            Thread t1 = new Thread(() => {
                for (int i = 0; i < 1000; i++)
                {
                    Counter++;
                    Thread.Sleep(1);
                }
            });
            t1.Start();

            Thread t2 = new Thread(() => {
                for (int i = 0; i < 1000; i++)
                {
                    Counter++;
                    Thread.Sleep(1);
                }
            });
            t2.Start();

            //等待线程结束
            while (!(t1.ThreadState == ThreadState.Stopped && t2.ThreadState == ThreadState.Stopped)) ;
            
            txbLog.AppendText(Counter.ToString() + "\\r\\n");
        }
    }

但是实际上你会发现,线程t1和线程t2结束后 Counter的值并不会是预期的2000,而且还会有波动,为什么?

假设t1线程读取到Counter的值为200,可能t2线程执行非常快,t1线程读取Counter值的时候,t2线程已经把Counter的值改为了205,等t1线程执行完毕以后,Counter的值又被变为了201,这样就会出现线程同步的问题了。那么该如何解决这个问题呢?

在这里插入图片描述

lock

lock是C#中的关键字,可以锁定一个资源,lock的特点是:同一时刻只能有一个线程进入lock的对象的范围,其它lock的线程都要等待。如果研究嵌入式,其实就是嵌入式RTOS中的互斥信号量

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        private static int Counter = 0;
        static object loker = new object();
        private void btnPrint_Click(object sender, EventArgs e)
        {
            Counter = 0;
            Thread t1 = new Thread(() => {
                for (int i = 0; i < 1000; i++)
                {
                    lock(loker)
                    {
                        Counter++;
                    }
                    
                    Thread.Sleep(1);
                }
            });
            t1.Start();

            Thread t2 = new Thread(() => {
                for (int i = 0; i < 1000; i++)
                {
                    lock (loker)
                    {
                        Counter++;
                    }
                    Thread.Sleep(1);
                }
            });
            t2.Start();

            //等待线程结束
            while (!(t1.ThreadState == ThreadState.Stopped && t2.ThreadState == ThreadState.Stopped)) ;
            
            txbLog.AppendText(Counter.ToString() + "\\r\\n");
        }
    }

添加lock后 结果符合预期
在这里插入图片描述

同步方法

除了使用关键字 lock外 还可以将方法标记为同步方法

public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        private static int Counter = 0;
        static object loker = new object();

        //将方法标记为同步方法
        [MethodImpl(MethodImplOptions.Synchronized)]
        static void Test()
        {
            Counter++;
        }
        private void btnPrint_Click(object sender, EventArgs e)
        {
            Counter = 0;
            Thread t1 = new Thread(() => {
                for (int i = 0; i < 1000; i++)
                {
                    Test();
                    Thread.Sleep(1);
                }
            });
            t1.Start();

            Thread t2 = new Thread(() => {
                for (int i = 0; i < 1000; i++)
                {
                    Test();
                    Thread.Sleep(1);
                }
            });
            t2.Start();

            //等待线程结束
            while (!(t1.ThreadState == ThreadState.Stopped && t2.ThreadState == ThreadState.Stopped)) ;
            
            txbLog.AppendText(Counter.ToString() + "\\r\\n");
        }
    }

利用委托自动创建线程

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

        //声明委托类
        delegate void txbLogPrintDelegate();

        void PrintfLog()
        {
            for (int i = 0; i < 10000; i++)
            {
                //通过txbLog的InVoke 告诉创建txbLog的线程,需要操作txbLog控件
                txbLog.Invoke((Action<string>)TxbLogAppendText, "这是第" + i + "行\\r\\n");
                Thread.Sleep(1);
            }
        }

        void TxbLogAppendText(string str)
        {
            //创建txbLog的线程也就是主线程 操作txbLog控件
            this.txbLog.AppendText(str);
        }

        private void btnPrint_Click(object sender, EventArgs e)
        {
            创建线程
            //Thread printThread = new Thread(PrintfLog);
            告诉系统,这个线程准备好了,可以开始执行了,至于什么时候执行,看系统安排
            //printThread.Start();

            //通过委托创建线程
            txbLogPrintDelegate testDelegate = PrintfLog;
            testDelegate.BeginInvoke(null, null);
        }
    }

同步与异步

利用委托自动创建线程时 有 BeginInvoke 和 Invoke两种,区别就是 Invoke是同步的,当工作线程运行结束才会执行下一行代码,BeginInvoke 是异步的。

带参数线程

使用thread 创建的线程,参数只能是 object类型的,object是所有类型的基类,因此可以装其他的所有类型,不过需要自己进行类型转换。

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

        //声明委托类
        delegate void txbLogPrintDelegate(int num);

        void PrintfLog(object num)
        {
            for (int i = 0; i < (int)num; i++)
            {
                //通过txbLog的InVoke 告诉创建txbLog的线程,需要操作txbLog控件
                txbLog.Invoke((Action<string>)TxbLogAppendText, "这是第" + i + "行\\r\\n");
                Thread.Sleep(1);
            }
        }

        void TxbLogAppendText(string str)
        {
            //创建txbLog的线程也就是主线程 操作txbLog控件
            this.txbLog.AppendText(str);
        }

        private void btnPrint_Click(object sender, EventArgs e)
        {
            //创建线程
            Thread printThread = new Thread(PrintfLog);
            //告诉系统,这个线程准备好了,可以开始执行了,至于什么时候执行,看系统安排
            printThread.Start(1000);

            通过委托创建线程
            //txbLogPrintDelegate testDelegate = PrintfLog;
            //testDelegate.BeginInvoke(100, null, null);
        }
    }

回调

当委托创建的异步线程执行结束时,想要在最后添加一个操作,可以利用异步委托的回调来实现(当然也可以将该操作添加到异步线程结束前,但是这样有可能会破坏模块封装性)。

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

        //声明委托类
        delegate void txbLogPrintDelegate(int num);

        void PrintfLog(int num)
        {
            for (int i = 0; i < (int)num; i++)
            {
                //通过txbLog的InVoke 告诉创建txbLog的线程,需要操作txbLog控件
                txbLog.Invoke((Action<string>)TxbLogAppendText, "这是第" + i + "行\\r\\n");
                Thread.Sleep(1);
            }
        }

        void TxbLogAppendText(string str)
        {
            //创建txbLog的线程也就是主线程 操作txbLog控件
            this.txbLog.AppendText(str);
        }

        private void btnPrint_Click(object sender, EventArgs e)
        {
            //通过委托创建线程
            txbLogPrintDelegate testDelegate = PrintfLog;
            //创建回调函数 异步线程结束时会调用该回调函数
            AsyncCallback asyncCallback = p => { txbLog.Invoke((Action<string>)TxbLogAppendText, "回调函数  结束\\r\\n"); };
            testDelegate.BeginInvoke(100, asyncCallback, null);
        }
    }

在这里插入图片描述

以上是关于C# 学习笔记线程的主要内容,如果未能解决你的问题,请参考以下文章

C# 学习笔记 控件的跨线程访问

C# 学习笔记 进程

各类技术学习笔记

C#学习笔记13

C#学习笔记---线程同步:互斥量信号量读写锁条件变量

学习笔记:python3,代码片段(2017)