zoukankan      html  css  js  c++  java
  • 简单地使用线程之一:使用异步编程模型

    .NetFramework的异步编程模型从本质上来说是使用线程池来完成异步的任务,异步委托、HttpWebRequest等都使用了异步模型。

    这里我们使用异步委托来说明异步编程模型。

    首先,我们来明确一下,对于多线程来说,我们需要关注哪些问题。

    “线程是一段执行中的代码流”,从这句话中,我们可以关注这段代码流何时开始执行、何时结束、从主线程如何传递参数至从子线程、从子线程如何返回结果至主线程?

    问题1:在异步编程模型中,子线程何时开始执行?

    对于异步编程模型来说,使用BeginXXX来开始执行线程。

    我们首先来看一个实例(来自《C#高级编程(第7版)》)。

    pulic delegate int TestMethodDelegate(int data,int ms);

    static int TestMethod(int data,int ms)

    {

        Console.WriteLine("TestMethod started.");

        Thread.Sleep(ms);

        Console.WriteLine("TestMethod completed.");

        return data++;

    }

    static Main()

    {

        TestMethodDelegate dl=TestMethod;

        IAsyncResult ar=dl.BeginInvoke(1,3000,null,null);

        //其中,1,3000分别对应委托的两个参数,后面两个null位置的参数是两个固定的参数,是留给异步回调的时候用的(稍后会讲到)。

    }

    问题2:在异步编程模型中,子线程何时结束?

    其实,这个问题对于只有一个主线程的单线程程序来说不会成为问题,因为一段代码执行过程中,执行到这个方法的最后一行,这个方法也就执行结束了。

    但在多线程中,由于主线程与子线程有一个从属关系,加上线程还有前台线程与后台线程之分,使得子线程的结束时机需要加以判断,判断规则如下。

    (1).如果子线程是后台线程,那么主线程结束时,不管子线程是否执行完毕,子线程将结束。

    (2).如果子线程是前强线程,那么主线程结束时,子线程将继续执行,直至结束。

    PS:由于线程池里面的线程均为后台线程,因此,异步编程模型中,只会存在第1种情况。

    那么问题来了:如果子线程为后台线程,且子线程的处理时间非常地长,那主线程如果处理到最后一条代码时,那子线程就会关闭,那子线程的处理逻辑就没走完,就会出错。

    我们该如何防止这样的问题发生呢?

    直观地来说,在主线程中等待子线程完成是一种可行的方案,而要实现这种方案,我们至少需要知道子线程是否已经执行完成?

    在异步编程模型中,我们可以使用轮询(Polling)、等待句柄(WaitHandler)、EndInvoke()方法来达到这样的效果。

    方法1:轮询(Polling)

    我们改写Main()方法如下:

    static Main()

    {

        TestMethodDelegate dl=TestMethod;

        IAsyncResult ar=dl.BeginInvoke(1,3000,null,null);

        while(!ar.IsCompleted)

        {

             //do something 

       }

       //执行到这里的时候,子线程已经执行完毕了。

    }

    方法2:等待句柄(WaitHandler)

    我们再次改写Main()方法。

    static Main()

    {

        TestMethodDelegate dl=TestMethod;

        IAsyncResult ar=dl.BeginInvoke(1,3000,null,null);

        while(true)

        {

             if(ar.AsyncWaitHandler.WaitOne(50,false))//每等待50毫秒,然后判断是否子线程是否已完成,如果已完成,则break。

            {

                  //执行到这里的时候,子线程已经执行完毕了。

                break;

            }

        }

       //主线程....

    }

    方法3:EndInvoke()方法

    再次改用Main()方法:

    static Main()

    {

        TestMethodDelegate dl=TestMethod;

        IAsyncResult ar=dl.BeginInvoke(1,3000,null,null);

        ar.EndInvoke();//会一直等待,直到委托方法执行完成,其实,也可以通过它将子线程的结果返回至主线程中,可以参考后面的问题4.

       //主线程....

    }

    问题3:在异步编程模型中,从主线程如何传递参数至从子线程?

    这个问题我们其实已经在问题1中有所展示,我们在BeginInvoke的方法中向委托中传递了两个参数。

    问题4:在异步编程模型中,从子线程如何返回结果至主线程?

    在问题2中,我们使用EndInvoke()等待委托方法执行完成,其实我们也可采用这个方法得到子线程的返回值。

    static Main()

    {

        TestMethodDelegate dl=TestMethod;

        IAsyncResult ar=dl.BeginInvoke(1,3000,null,null);

        int result=ar.EndInvoke();//得到返回值,返回值类型这里为int,即委托定义的返回值类型。    

       //主线程....

    }

    问题5:异步回调

    异步回调很有用,它的出现应该说是极大地扩展了异步模型下的多线程威力。为什么这么说?

    考虑一种情况,如果主线程需要120秒完成执行,子线程需要80秒,在主线程第90秒的时候开始有启动子线程,主线程等待子线程结束,最后结束主线程。那么,整个主线程就至少需要90+80秒的时间来完成执行,中间很明显有不少主线程等待子线程的时间。有没有更好的办法?

    如果主线程不需要子线程的返回结果,而子线程结束后还会有一些单独的处理逻辑(比如清理对象、单独的处理数据等),我们可以采用异步回调来解决这个问题。

    异步回调:简而言之,就是异步任务完成之后,再回过头来调用的方法。

    先看一个异步回调的例子:

    static Main()

    {

        TestMethodDelegate dl=TestMethod;

        IAsyncResult ar=dl.BeginInvoke(1,3000,TestMethodCompleted,dl);

       //主线程....

    }

    static TestMethodCompleted(IAsyncResult ar)

    {

         //do something

        TestMethodDelegate dl=(TestMethodDelegate )ar.AysncState;

        dl.EndInvoke();//可以传递委托实例进来,在这里获得委托线程的结果。    

    }

    在这里,我们关注BeginInvoke方法的第3个和第4个参数。

    第3个参数,TestMethodCompleted即是回调方法,它是一个AsyncCallback类型的委托。

    第4个参数是传递给回调方法的参数,在回调方法中可以用ar.AsyncState来获得这个参数。

    使用回调方法,必须注意这个方法要从委托线程中调用,而不是从主线程中调用,如果从主线程中调用,那就变成了普通的方法调用。

    另外,如果使用Lambda表达式可以实现一些更简洁优雅的偌。 

    static Main() 

        TestMethodDelegate dl=TestMethod; 

        dl.BeginInvoke(1,3000,

            ar=>{

                         int result=dl.EndInvoke(ar);

                         //do something

                    },

            null) ;

       //主线程.... 

    //这里,不需要把一个值赋予BeginInvoke()方法的最后一个参数,因为Lambda表达式可以直接访问该作用域外部的变量dl。但是,Lambsa表达式的实现代码仍是从委托线程中调用,只是不是很明显。

  • 相关阅读:
    微信授权,重定向两次
    Newtonsoft.Json 序列化 排除指定字段或只序列化指定字段
    各大快递公司面单号准确性验证的正则表达式,来自淘宝开放平台,时间是20181206,
    微信小程序web-view(webview) 嵌套H5页面 唤起微信支付的实现方案
    HTTP请求头及其作用 转
    sql server 只读帐号设置能读取存储过程,view等内容。
    PhantomJS命令行选项
    XML实体注入漏洞
    XmlDocument 避免XXE
    Centos7.6安装redis
  • 原文地址:https://www.cnblogs.com/gudi/p/4499155.html
Copyright © 2011-2022 走看看