C#中关于多线程、同步和异步的几点说明

目录

关于线程、同步、异步的相关知识点

跨线程操作的案例

多线程操作案例

线程池的使用案例

同步和异步操作案例


关于线程、同步、异步的相关知识点

本文不做过多描述,参见下述链接

线程:C#多线程 - .NET开发菜鸟 - 博客园

创建多线程的步骤:

1、编写线程所要执行的方法

2、实例化Thread类,并传入一个指向线程所要执行方法的委托。(这时线程已经产生,但还没有运行)

3、调用Thread实例的Start方法,标记该线程可以被CPU执行了,但具体执行时间由CPU决定

ThreadStart是一个无参的、返回值为void的委托,可以运行静态的方法,也可以运行实例方法

ParameterizedThreadStart是一个有参的、返回值为void的委托,注意若创建有参的方法,注意,方法里面的参数类型必须是Object类型。

使用线程,命名空间开头要加上 using System.Threading;

多线程和异步操作的异同

多线程和异步操作两者都可以达到避免调用线程阻塞的目的,从而提高软件的可响应性。甚至有些时候我们就认为多线程和异步操作是等同的概念。但是,多线程和异步操作还是有一些区别的。而这些区别造成了使用多线程和异步操作的时机的区别。

更多说明参考下述文章:

线程的同步和异步 - Bliss丶boy - 博客园

C#多线程和异步(三)——一些异步编程模式 - 捞月亮的猴子 - 博客园

委托的BeginInvoke和EndInvoke方法 - canger - 博客园

C# action,delegate,func的用法和区别 - .net&new - 博客园

关于线程池的使用

C#多线程--线程池(ThreadPool) - 从未被超越 - 博客园

利用log4net记录日志

winform项目中使用log4net_hewusheng10的专栏-CSDN博客

关于委托的使用区别:

在C#中实现委托有三种方式:delegate,Action和Func,推荐使用Action和Func

delegate委托使用过程:声明一个委托、实例化一个委托、使用一个委托

Action委托必须是无返回值的方法

Func委托必须有返回值的方法


跨线程操作的案例

下面是跨线程操作的例子,后几种方法不需要提前定义和使用回调,使用更简便

跨线程访问方法1:

        //定义用于跨线程的回调
        private delegate void SetRichValueCallBack(string value);
        //声明回调
        SetRichValueCallBack setCallBack;

        // 定义回调委托需要执行的方法
        private void SetRichValue(string value)
        {
            richTextBox1.AppendText(value + "\r");
        }

        private void button7_Click(object sender, EventArgs e)
        {
            //实例化回调,括号内填回调的方法
            setCallBack = new SetRichValueCallBack(SetRichValue);
            //创建一个线程去执行这个方法:创建的线程默认是前台线程
            Thread t1 = new Thread(new ThreadStart(NumTest1));
            //Start方法标记这个线程就绪了,可以随时被执行,具体什么时候执行这个线程,由CPU决定
            //将线程设置为后台线程
            t1.IsBackground = true;
            t1.Start();


            Thread t2 = new Thread(new ThreadStart(NumTest2));
            t2.IsBackground = true;
            t2.Start();

            Thread t3 = new Thread(new ThreadStart(NumTest3));
            t3.IsBackground = true;
            t3.Start();

            Thread t4 = new Thread(new ThreadStart(NumTest4));
            t4.IsBackground = true;
            t4.Start();

            Thread t5 = new Thread(new ThreadStart(NumTest5));
            t5.IsBackground = true;
            t5.Start();
        }

        private void NumTest1()
        {
            for (int i= 10;i< 20;i++)
            {
                //使用回调
                Thread.Sleep(500);
                richTextBox1.Invoke(setCallBack, i.ToString());
            }
        }

跨线程访问简化代码1:直观地在Invoke方法调用的时候就看到具体的函数

        private void NumTest2()
        {
            for (int i = 20; i < 30; i++)
            {
                //使用回调
                Thread.Sleep(500);
                richTextBox1.Invoke( new EventHandler(delegate
                {
                    richTextBox1.AppendText(i.ToString()+"\r");
                }));
            }
        }

跨线程访问简化代码2:基于简化方法1,richTextBox1.Invoke变为this.Invoke

        private void NumTest3()
        {
            for (int i = 30; i < 40; i++)
            {
                //使用回调
                Thread.Sleep(500);
                this.Invoke(new EventHandler(delegate
                {
                    richTextBox1.AppendText(i.ToString() + "\r");
                }));
            }
        }

跨线程访问简化代码3:采用action的写法(推荐)

        //定义要执行的方法
        private void SetRichValue2(string value)
        {
            richTextBox1.AppendText(value + "\r");
        }

        //跨线程访问方法4:action的写法
        private void NumTest4()
        {
            Action<string> action = this.SetRichValue2;
            //Action<string> action = new Action<string>(SetRichValue2);//与上面写法相同          
            for (int i = 40; i < 50; i++)
            {
                Thread.Sleep(500);
                // 调用委托
                this.Invoke(action,i.ToString());
            }
        }

跨线程访问简化代码4:在C#3.0及以后的版本中有了Lamda表达式,像上面这种匿名委托有了更简洁的写法

        private void NumTest5()
        {
            for (int i = 50; i < 60; i++)
            {
                //使用回调
                Thread.Sleep(500);
                this.Invoke(new Action(()=>
                {
                    richTextBox1.AppendText(i.ToString() + "\r");
                }));
            }
        }

上述代码运行效果如下图


多线程操作案例

多线程同时操作的一个例子:多线程同时图书购买

        private int bookNum = 0;

        //库存数量设定
        private void button9_Click(object sender, EventArgs e)
        {
            bookNum = int.Parse(textBox1.Text);
            richTextBox1.AppendText("库存量" + bookNum.ToString() + "设定成功!" + "\r");
            textBox2.Text = bookNum.ToString();
        }

        //线程1购买图书
        private void button3_Click(object sender, EventArgs e)
        {
            BookBuyThread("线程1");
        }
        //线程2购买图书
        private void button4_Click(object sender, EventArgs e)
        {
            BookBuyThread("线程2");
        }

        //多线程购买,根据要设定的数量自动创建多个线程
        private void button8_Click(object sender, EventArgs e)
        {
            int threadNum = int.Parse(textBox3.Text);
            if (threadNum >= 1)
            {
                for (int i = 1; i <= threadNum; i++)
                {
                    BookBuyThread("自动多线程" + i.ToString());
                }
            }
            else
            {
                richTextBox1.AppendText("多线程数量不合格!" + "\r");
            }
        }

        //定义开启新线程的方法
        private void BookBuyThread(string buyerName)
        {
            Thread t1 = new Thread(new ThreadStart(BookSale));
            t1.Name = buyerName;
            t1.IsBackground = true;
            t1.Start();
        }


        // 创建有参的方法,注意,方法里面的参数类型必须是Object类型
        private void BookSale()
        {
            //使用lock关键字解决线程同步问题,使得某一时刻只有一个线程可以访问变量
            lock (this)
            {
                string buyerName = Thread.CurrentThread.Name;
                if (bookNum > 0)//判断是否有书,如果有就可以卖
                {
                    Thread.Sleep(100);
                    bookNum -= 1;                    
                    this.Invoke(new Action(() =>
                    {
                        richTextBox1.AppendText(buyerName + "购买成交!" + "\r");
                        textBox2.Text = bookNum.ToString();
                    }));
                }
                else
                {
                    this.Invoke(new Action(() =>
                    {
                        richTextBox1.AppendText(buyerName + "购买失败!库存不足" + "\r");
                    }));
                }
            }
        }

线程池的使用案例

定义线程池执行的回调方法

        //获取线程池结束状态用
        AutoResetEvent myEvent = new AutoResetEvent(false); 
        private int forThreadEnd;
        //定义线程池执行的回调方法
        private void ThreadPoolCallback(object obj)
        {
            Thread.Sleep(2000);
            log4netHelper.Info(DateTime.Now.ToString() + " 当前线程" + obj.ToString());

            //下面代码用于判断线程池内线程是否执行完毕
            forThreadEnd -= 1;
            if (forThreadEnd == 0)
            {
                 myEvent.Set();
            }
        }

线程池的使用

        private void ThreadPoolTest()
        {
            int cycleNum = 10; 
            forThreadEnd = cycleNum;
            ThreadPool.SetMinThreads(1,1); //定义线程池最小数量
            ThreadPool.SetMaxThreads(5,5); //定义线程池同时执行最大数量
            for (int i=1;i<=cycleNum;i++)
            {
                //把需要执行的方法加入到线程池
                ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadPoolCallback), i.ToString());
            }
            richTextBox1.AppendText("主线程开始" + DateTime.Now.ToString() + "\r");
            myEvent.WaitOne();           
            richTextBox1.AppendText("线程池结束"+ DateTime.Now.ToString() + "\r");
        }

说明:上述代码执行后,创建一个线程池含10个线程,分2组执行完毕


同步和异步操作案例

同步和异步是对方法执行顺序的描述。

同步:等待上一行完成计算之后,才会进入下一行;

异步:不会等待方法的完成,会直接进入下一行,是非阻塞的;

下面是一个简单的操作案例:

首先定义一些基本参数

        //获取当前系统时间
        private string GetCurTIme()
        {
            return DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
        }
        //查询当前进程ID值
        private int GetThreadID()
        {
            return Thread.CurrentThread.ManagedThreadId;
        }
        private void RichInfoAdd(string value)
        {
            richTextBox1.AppendText(value + "进程编号" + GetThreadID() + " " + GetCurTIme() + "\r");
        }
        //定义执行次数
        private int cycleTime = 3;

        //定义要执行的方法
        private string TestMethod(string name)
        {
            Thread.Sleep(1000);
            return "DoSL" + name + " end   " + "进程编号" + GetThreadID() + " " + GetCurTIme() + "\r";
        }

同步操作,所有操作信息一次打印,都在一个线程中执行,时间较长

        //同步操作,所有操作信息一次打印,都在一个线程中执行,时间较长
        private void button1_Click(object sender, EventArgs e)
        {
            RichInfoAdd("同步操作start ");
            
            for (int i=0;i<cycleTime;i++)
            {
                string name="同步操作指令"+i;
                richTextBox1.AppendText(TestMethod(name));
                
            }
            RichInfoAdd("同步操作end  ");
        }

异步操作,所有操作信息同时打印,约一个for循环的时间(正确用法)

        //异步操作,所有操作信息同时打印,约一个for循环的时间
        private void button5_Click(object sender, EventArgs e)
        {
            RichInfoAdd("异步操作 start ");

            Func<string, string> function = this.TestMethod;
            IAsyncResult[] result = new IAsyncResult[cycleTime];

            //第一个for循环用于创建异步程序
            for (int i = 0; i < cycleTime; i++)
            {
                result[i] = function.BeginInvoke("异步操作指令 " + i, null, null);
            }

            for (int j = 0; j < cycleTime; j++)
            {
                richTextBox1.AppendText(function.EndInvoke(result[j]));
            }
            RichInfoAdd("异步操作 end  ");
        }

异步操作,下面的代码,虽然操作信息一条一条打印,由于异步开始和接收指令都写在了一个for循环中,总时间与同步操作一样

        //异步操作,操作信息一条一条打印,由于异步开始和接收指令都写在了一个for循环中,总时间与同步操作一样
        private delegate string AsyncDelegate(string value);
        private void button2_Click(object sender, EventArgs e)
        {
            RichInfoAdd("异步操作方式1 start ");

            AsyncDelegate asyncDel =new AsyncDelegate(TestMethod);
            for (int i = 0; i < cycleTime; i++)
            {
                string name = "异步操作指令 " + i;
                IAsyncResult result = asyncDel.BeginInvoke(name, null, null);
                string returnStr = asyncDel.EndInvoke(result);
                richTextBox1.AppendText(returnStr);      
            }
            RichInfoAdd("异步操作方式1 end  ");
        }

异步操作,与上一条效果相同,不同的是采用func指令

        //异步操作,与上一条效果相同,不同的是采用func指令
        private void button4_Click(object sender, EventArgs e)
        {
            RichInfoAdd("异步操作方式2 start ");

            Func<string, string> function = this.TestMethod;
            for (int i = 0; i < cycleTime; i++)
            {
                string name = "异步操作指令 " + i;
                IAsyncResult result = function.BeginInvoke(name, null, null);
                string returnStr = function.EndInvoke(result);
                //string returnStr = function.EndInvoke(function.BeginInvoke(name, null, null));
                richTextBox1.AppendText(returnStr);
            }
            RichInfoAdd("异步操作方式2 end  ");
        }

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值