欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C#多线程编程笔记

程序员文章站 2022-08-09 10:19:09
在开发中经常有遇到因为程序执行的时间过长,而造成程序假死的情况,这是因为我们的程序是同步执行的,当执行到需要长时间的操作时,程序就会等待当前的操作完成,从而造成程序假死。C#的异步与多线程就是为了解决这个问题的。 多线程编程示例 .Net提供了多种方式实现多线程的编程,包括线程池,Thread,Ta ......

在开发中经常有遇到因为程序执行的时间过长,而造成程序假死的情况,这是因为我们的程序是同步执行的,当执行到需要长时间的操作时,程序就会等待当前的操作完成,从而造成程序假死。c#的异步与多线程就是为了解决这个问题的。

  1. 什么是多线程,举个简单的例子,我们在做饭的时候,可以先煮好饭,然后炒菜,然后洗餐具,然后完成,每一个操作都是在前一个操作完成之后才能进行,这就叫做同步执行,我们也可以在边煮饭的同时炒菜,洗餐具,当所有的工作都做完的时候,饭也就做好了,在这个过程中,煮饭,炒菜是同时进行的,这个就是异步,多线程就类似于主线程在煮饭,然后另开一个新的线程用来炒菜。
  2. 多线程的优缺点,由上所述,多线程可以在新开的线程中执行操作,所以他不需要等到主线程完成,也不收主线程的影响,自然,也就不会造成程序假死的情况出现了。这样可以提高用户的交互体验,防止用户以为程序崩溃也不断重启。但是由于我们直接感受到的是主线程,而新开的线程其实是在后台执行的,所以,如果新开的线程出现了异常,我们很难再主线程中捕获,或者处理,同时新开一个线程也需要计算机硬件的支持,如果线程过多,可能会造成系统变得很卡,资源消费过多的现象。
  3. 什么情况下需要使用多线程的技术?根据多线程的特点,其实类似于一个后台执行的任务,所以一般在以下情况中会使用多线程技术。1、程序执行时间比较长,同时程序的结果不是那么重要,不应该是主线程等待结果的情况下,可以使用多线程异步执行。例如,登录之后的验证过程。2、需要定时刷新的功能,这些功能是定时循环执行,所以可以放在后台去异步执行,这样既能保证功能执行了,同时在执行的过程中也不会造成程序卡顿的现象。3、后台任务,程序只需要执行相关的功能,不需要接收执行结果。例如发送一条短信,我们只需要发送出去即可,不需要知道用户是否接收到了短信,这样的情况下可以使用异步发送。其他情况可以参考以上的情况来决定是否需要使用多线程。

多线程编程示例

.net提供了多种方式实现多线程的编程,包括线程池,thread,task等方法,下面对应这些方法给出简单的示例。

首先,建立一个功能类,此类的作用是多线程需要执行的方法,方法包括,无参数无返回值,有参数无返回值,无参数有返回值,有参数有返回值四种情况。

  /// <summary>

    /// 此类用于多线程测试的公用方法的定义

    /// </summary>

    public class commonclass

    {

        //注意多线程使用的方法对应的参数类型应该为object

        /// <summary>

        /// 无返回值有参数的方法,用于无返回值的多线程的类型的使用

        /// </summary>

        /// <param name="name"></param>

        public void showname(object name)

        {

            console.writeline("your name is: {0}", name);

        }

        /// <summary>

        /// 有返回值的方法,用于有返回值的多线程类型的使用

        /// </summary>

        /// <param name="name"></param>

        /// <returns></returns>

        public string getname(object name)

        {

            return name + "english's name is lily";

        }

}

上面的代码只有两个方法,都带参数,但是一个有返回值,一个没有返回值。对于无参数的方法,只需要把传入的参数设置为null即可。

  1. 使用threadpool实现多线程

threadpool顾名思义就是线程池,由于创建一个新的线程的代价比较大,所以如果没有必要,就不需要创建一个新的线程。线程池就是为此设计的,当新创建一个线程的时候,首先到线程池中查询是否有空闲线程,如有,则直接使用此线程,这样就避免了新创建一个线程,若无,则新创建一个线程,并加入到线程池中,当线程执行结束之后,当前线程变为空闲线程,并放入线程池,等待下一个调动。当创建的线程数超过线程池允许的最大线程数之后,线程就需要排队,等待空闲线程的出现。

/// <summary>

    /// 线程池实现多线程的定义

    /// 线程池与thread的区别在于,thread每次都是新建一个线程,执行完成后就销毁

    /// 而线程池有一个最大线程数,会在池内空闲线程数不够的时候创建新的线程,并存入

    /// 线程池,线程执行完成后并不会销毁,而是存入线程池,作为空闲线程,等待下一次调用,

    /// 超过线程池最大线程数时,不会再创建新的线程,而是排队等待新的空闲线程,因此,

    /// 线程池比thread的性能更好

    /// </summary>

    public class threadpooltest

    {

        //和thread一样,线程池只能创建无返回值和最多带一个参数的多线程方法

        public void createthread()

        {

            //threadpool.setmaxthreads(10, 10);//设置线程池最大线程数的方法

            //threadpool.setminthreads(5, 5);//设置线程池最小线程数的方法

            var com = new commonclass();

            for (int i = 2; i < 7; i++)

            {

                //往线程池中添加5个方法,但是线程池中不一定会创建5个线程

                threadpool.queueuserworkitem(new waitcallback(com.showname), i.tostring());

            }

        }

}

线程池的方法可以有参数,但是不能有返回值,或者只能返回void。

  1. thread类实现多线程

thread用于创建一个线程,他与线程池的区别就在于,他每次都会新创建一个线程,执行完成之后销毁线程。不存在等待空闲线程的概念,性能取决于硬件设备的性能。

/// <summary>

    /// 此类用于多线程的thread类实现测试

    /// </summary>

    public class threadtest

    {

        //thread不能创建带有返回值的多线程方法,如果需要请使用task

        /// <summary>

        /// 用于创建多线程并行的代码

        /// </summary>

        public void createthread()

        {

            var com = new commonclass();

            for (int i = 0; i < 5; i++)

            {

                //parameterizedthreadstart类型用于定义一个带参数的方法的线程,如果需要定义不带参数的多线程实现,请使用threadstart

                //同时参数只能有一个,如果需要使用多个参数,请使用其他多线程实现方法,或将多个参数直接封装为一个class进行传递

                thread t = new thread(new parameterizedthreadstart(com.showname));

                t.start(i.tostring());

            }

        }

}

上面代码中红字部分为要执行的方法,从字面意思即可知道,这个方法是需要定义参数的,如果需要定义无参数的方法,则需要使用new threadstart(方法名),t.start()方法用于开始执行线程,方法的参数即为调用的方法需要传入的参数。在上面的示例中,传入的参数即为showname方法所需的参数。和threadpool一样,thread也是不能有返回值的。

  1. task实现多线程

task即为任务,也是实现多线程的一种方式,他定义的方法必须带一个object的参数,同时可以有返回值。

/// <summary>

    /// 通过task实现多线程的方法

    /// task相比thread和threadpool的区别最直观的就在于可以实现带返回值的方法

    /// </summary>

    public class tasktest

    {

        /// <summary>

        /// 使用task创建不带返回值的多线程

        /// 此处的void也可以为task,在async的异步编程中必须为task

        /// 具体实现请参考createreturtaskthread

        /// </summary>

        public void createnoreturnthread()

        {

            for (int i = 0; i < 5; i++)

            {

                //此处会形成一个闭包,所以多次返回的结果一样

                task.run(() =>

                {

                    console.writeline("this number is {0}", i);

                });

                //可以通过如下委托的方式解决上面的问题

                //action<int> act = (a) =>

                //{

                //    task.run(() =>

                //    {

                //        console.writeline("this number is {0}", a);

                //    });

                //};

                //act(i);

            }

        }

        /// <summary>

        /// 返回task的方法

        /// </summary>

        /// <returns></returns>

        public task createreturtaskthread()

        {

            return task.run(() =>

            {

                console.writeline("this is a method for return task!");

            });

        }

        /// <summary>

        /// 创建返回具体值的方法

        /// </summary>

        /// <returns></returns>

        public task<string> createreturnnamethread()

        {

            return task.run(() =>

            {

                commonclass com = new commonclass();

                string name = com.getname("hos ");

                thread.sleep(5000);

                return name;

            });

        }

}

在上面的示例中,既有返回void的方法,也有返回具体值的方法,使用task.run方法即可定义一个异步执行的方法。run内部需要传入一个委托,来定义需要异步执行的功能,相比较thread,代码的是想相对复杂,但是可以有返回值,同时创建一个任务相比建创建一个线程的开销小很多。

  1. async与await关键字

在c#4.0以后为简化异步操作,添加可async与await两个关键字,这两个关键字的内部也是通过task来实现异步操作,但是如果不添加这两个关键字,那么方法就会以同步执行的方式来执行。同时await会等待方法执行的结果,但是在等待的过程中,不会阻塞主线程,也就不会造成程序假死的现象。

/// <summary>

    /// 此类用于测试.net4.0的async实现异步编程的方法

    /// </summary>

    public class asynctest

    {

        //1.定义一个返回值为task<t>的方法

        public task<int> getsum(list<int> list)

        {

            return task.run(() =>

            {

                return list.sum();

            });

        }

        //2.定义一个标识了async的方法

        /// <summary>

        /// 此方法用async标识,代表这是一个异步执行的方法,此方法不会阻塞当前线程

        /// </summary>

        public async void showsum()

        {

            list<int> list = new list<int>{ 5, 15, 12, 7, 9, 13, 6, 21 };

            //此代码加了await标识,与async配合使用,代表这是一个异步的方法,

            //如果不加await方法,则此处代码会同步执行

            //需要注意的是await后面的方法需要等到await执行完成之后才会继续执行,

            //而不是和后面的代码一起执行,也就是说这里实现了间接的多线程的同步的功能,

            //同时不会卡住主线程,可以解决winform项目中界面的假死的问题

            //因为使用了await关键字,所以不需要在使用.result来获取task的结果了

            int result = await getsum(list);

            console.writeline("result is {0}",result);

        }

}

如上代码所示async是需要添加在方法的定义上面,同时微软建议,所有的async标识的方法返回的都应该是task<t>如果是返回void则返回task,上面的方法仅做演示所以未遵照此要求,需要注意。await关键字放在需要异步执行的方法之前即可。

以上就是几种实现多线程的方式。调用的方法也很简单

//注意:多线程由于是异步执行,所以可能造成无法预知的执行顺序,即以下代码每次执行的结果都可能不同

            //1.thread实现

            //threadtest.common.threadtest tt = new common.threadtest();

            //tt.createthread();

 

            //2.threadpool实现

            //threadpooltest tpt = new threadpooltest();

            //tpt.createthread();

 

            //3.task实现

            //3.1 无返回值的实现

            //tasktest tt = new tasktest();

            //tt.createnoreturnthread();

            //3.2 返回一个task的实现

            //var result = tt.createreturtaskthread();

            //3.3 返回一个task<t>的实现

            //var result = tt.createreturnnamethread();

            //console.writeline(result.result);

 

            //4.async的实现

            //var at = new asynctest();

            //at.showsum();

 

            //5.多线程的同步实现

            //5.1 autoreseteventtest

            //locktest lt = new locktest();

            //lt.autoreseteventtest();

            //5.2 mutextest

            //lt.mutextest();

            //5.3 mullock

            //lt.mullock();

            //5.3  manualresetevent实现线程的手动挂起

            //lt.mullockm();

 

            asynctest at = new asynctest();

            at.showsumno();

            console.readkey();

上面的代码仅供学习使用,如果需要更加深入的了解各种方式,请参考相关的教程,谢谢!