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

C#基础之多线程与异步

程序员文章站 2022-05-29 10:51:23
1.基本概念 多线程与异步是两个不同概念,之所以把这两个放在一起学习,是因为这两者虽然有区别,但也有一定联系。 多线程是一个技术概念,相对于单线程而言,多线程是多个单线程同时处理逻辑。例如,假如说一个人把水从A地提到B点可看作是单线程,那么如果两个人同时去做事(可以是相同的一件事,也可以是不同的一件 ......

 1.基本概念

多线程与异步是两个不同概念,之所以把这两个放在一起学习,是因为这两者虽然有区别,但也有一定联系。

    多线程是一个技术概念,相对于单线程而言,多线程是多个单线程同时处理逻辑。例如,假如说一个人把水从a地提到b点可看作是单线程,那么如果两个人同时去做事(可以是相同的一件事,也可以是不同的一件事)就可以看作是两个线程。

    异步:记得读书时学过一篇课文叫《统筹方法》,里面讲述煮茶喝的过程,如下:

    比如,想泡壶茶喝。当时的情况是:开水没有;水壶要洗,茶壶、茶杯要洗;火已生了,茶叶也有了。怎么办?

    办法甲:洗好水壶,灌上凉水,放在火上;在等待水开的时间里,洗茶壶、洗茶杯、拿茶叶;等水开了,泡茶喝。

    办法乙:先做好一些准备工作,洗水壶,洗茶壶茶杯,拿茶叶;一切就绪,灌水烧水;坐待水开了,泡茶喝。

    那么办法甲就是异步方法,办法乙就是同步方法。利用多线程就可以实现办法甲:如用a线程做“洗好水壶,灌上凉水,放在火上”,另b线程做“洗茶壶、洗茶杯、拿茶叶”,并在b线程中等待a线程执行完毕(即水开),再继续做“泡茶喝”。

    上面是示例讲法,所谓的“等待”翻译成技术语言就是“阻塞”,异步与同步的区别就是看一事件中是否有阻塞,等待另一件事情的处理结果。从示例中也可以看到,异步中用到了多线程,但异步和多线程显然是两个不同的概念。

结论:异步是相对同步来说的一个目的,而多线程是实现这个目的一种技术方法。

 

2.task的运用

task是.net framework 4.5加入的概念,之前实现多线程是利用thread类,在此只对task进行学习,在实际编码中基本用task,因为它比thread更易理解,更易运用,更安全可靠。

下面是两者差异的一个总结:

1.task与thread对比,task相当于应用层,thread更底层,但二者是不一样的,没有隶属关系
2.task是在线程池上创建,是后台线程(主线程不会等其完成);thread是单个线程,默认是前台线程
3.task可以直接获取返回值,thread不能直接从方法返回结果(可以使用变量来获取结果)
4.使用task.continuewith()方法可以继续执行其他任务。线程中无连续性,当线程完成工作时,不能告诉线程开始其他操作。 尽管可以使用join()等待线程完成,但是这会阻塞主线程
5.task借助cancellationtokesource类可以支持任务中的取消,当thread处于运行中时,无法取消它
6.task能方便捕捉到运行中的异常,thread在父方法中无法捕捉到异常

 下面用示例代码来展示task实现异步的基本运用:

 public static void main()
        {
            // start the handlefile method.
            task<int> task = handlefileasync();//可以看作是一个耗时任务

            // control returns here before handlefileasync returns.
            // ... prompt the user.
            console.writeline("please wait patiently " +
                "while i do something important.");

            // do something at the same time as the file is being read.
            string line = console.readline();
            console.writeline("you entered (asynchronous logic): " + line);

            // wait for the handlefile task to complete.
            // ... display its results.
            task.wait();//有调用.result时,这里可以省略。在没有调用.result时,一定要.wait()下,这个话题涉及到当task运行出现异常时:为什么要调用wait或者result?
或者一直不查询task的exception属性?你的代码就永远注意不到这个异常的发生,如果不能捕捉到这个异常,垃圾回收时,
抛出aggregateexception,进程就会立即终止,这就是“牵一发动全身”,莫名其妙程序就自己关掉了 var x = task.result;//其实在用result的时候,内部会调用wait console.writeline("count: " + x); console.writeline("[done]"); console.readline(); } static async task<int> handlefileasync()//async与下文的await是成对出现,否则不能实现真正的异步,而是同步执行。 { string file = @"c:\enable1.txt";//在这个文档中输入几个字符和输入1mb字符,最终输出结果会有不同,能很好的展示异步调用方式 console.writeline("handlefile enter"); int count = 0; // read in the specified file. // ... use async streamreader method. using (streamreader reader = new streamreader(file)) { string v = await reader.readtoendasync(); //string v = reader.readtoend(); // ... process the file data somehow. count += v.length; // ... a slow-running computation. // dummy code. for (int i = 0; i < 10000; i++) { int x = v.gethashcode(); if (x == 0) { count--; } } } console.writeline("handlefile exit"); return count; }

上面是task的基本运用,看看注释了解一些注意事项。下面是对continuewith,即连续任务的一个运用,如下:

  static void main()
        {
            // call async method 10 times.
            for (int i = 0; i < 10; i++)
            {
                run2methods(i);
            }
            // the calls are all asynchronous, so they can end at any time.
            console.readline();
        }

        static async void run2methods(int count)
        {
            // run a task that calls a method, then calls another method with continuewith.
            int result = await task.run(() => getsum(count))
                .continuewith(task => multiplynegative1(task));
            console.writeline("run2methods result: " + result);
        }

        static int getsum(int count)
        {
            // this method is called first, and returns an int.
            int sum = 0;
            for (int z = 0; z < count; z++)
            {
                sum += (int)math.pow(z, 2);
            }
            return sum;
        }

        static int multiplynegative1(task<int> task)
        {
            // this method is called second, and returns a negative int.
            return task.result * -1;
        }

下面是如果想取消任务时cancellationtokensource类的运用:

  static void main(string[] args)
        {
            using (var cts = new cancellationtokensource())
            {
                task task = new task(() => { longrunningtask(cts.token); });
                task.start();
                console.writeline("operation performing...");
                if (console.readkey().key == consolekey.c)
                {
                    console.writeline("cancelling..");
                    cts.cancel();
                }
                console.read();
            }
        }
        private static void longrunningtask(cancellationtoken token)
        {
            for (int i = 0; i < 10000000; i++)
            {
                if (token.iscancellationrequested)
                {
                    break;
                }
                else
                {
                    console.writeline(i);
                }
            }
        }
以上是task的基本运用。


3.总结
以上是利用task、async、await实现异步的方法,示例三展示的是task实现任务的取消,这里不是一个异步方法,只是一个单纯的任务取消(可理解为多线程的取消)。task本质是类似于threadpool的一个线程池,只需把要做的事丢进去,底层线程怎么分配怎么运行,编码时完全不用关心。如果是用thread类,就需要自己去控制线程的启停销毁等,控制不好就会“翻车”,而且这种“翻车”会导致出现莫名其妙的结果,甚至程序锁死,因此在实际编码中尽量使用task类。