线程(1)

来源:互联网 发布:js修改字体颜色 编辑:程序博客网 时间:2024/05/22 08:03

1.为什么要使用线程和同步

     使用线程有几个原因。假设从应用程序中进行网络调用需要一定的时间。用户不希望分割用户界,并且让用户一直等待直到从服务器返回一个响应为止。用户可以同时执行其他一些操作,或者甚至取消发送给服务器的请求。这些都可以使用线程来实现。
     
对于所有需要等待的操作,例如,因为文件、数据库或网络访问都需要一定的时间,此时就可以启动一个新线程,同时完成其他任务。即使是处理密集型的任务,线程也是有帮助的。一个进程的多个线程可以同时运行在不同的CPU,或多核CPU的不同内核上。

     还必须注意运行多个线程时的一些问题。它们可以同时运行,但如果线程访问相同的数据,就很容易出问题。必须实现同步机制。

2.线程

  (1)异步委托

   创建线程的一种简单方式是定义一个委托,并异步调用它。委托是方法的类型安全的引用。Delegate类还支持异步地调用方法。在后台Delegate类会创建一个执行任务的线程。

   使用不同的技术异步地调用委托,并返回结果。


   1.投票

检查委托是否完成了它的任务。

Delegate类提供了BeginInvoke()方法,在该方法中,可以传递用委托类型定义的输入参数。

BeginInvoke()方法总是有AsyncCdback和object类型的两个额外参数(稍后讨论)。现在重要的是BeginInvoke()

方法的返回类型:IAsyncResult。通过IAsyncResdt,可以获得该委托的相关信息,并验证该委托是否完成了任务,

这是IsCompleted属性的功劳。

EndInvoke()用于返回委托方法的执行结果,EndInvoke()方法会一直等到委托完成其任务为止。

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

while(!ar.IsCompleted)

    {

       Console.WriteLine(".");

       Thread.Sleep(50);

    }

 

    while (true)

     {

       Console.WriteLine(".");

       if(ar.AsyncWaitHandle.WaitOne(50,false))

         {

            Console.WriteLine("CAn Get TheResult");

         break;

       }

     }

 

    int result = dl.EndInvoke(ar);

    Console.WriteLine(result);

 

   2.等待句柄

   使用AsyncWaitHandle属性可以访问等待句柄。这个属性返回一个WaitHandle类型的对象,它可以等待委托线程完成其任务。而WaitOne()方法将一个超时时间作为可选的第一个参数,在其中可以定义要等待的最长时间。这里设置为5O毫秒。如果发生超时, WaitOne()方法就返回false.同样使用委托的EndInvokeo方法接收结果。

 

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

if (ar.AsyncWaitHandle.WaitOne(50,false))

     {

      Console.WriteLine("CAn Get TheResult");

      break;

     }

  int result = dl.EndInvoke(ar);

Console.WriteLine(result);

   3.异步回调

   在BeginInvoke()方法的第三个参数中,可以传递一个满足AsyncCallback委托的需求方法。AsyncCauback委托定义了一个IAsyncResult类型的参数,其返回类型是void。 对于BeginInvoke()方法最后一个参数,可以传递任意对象,以便从回调方法中访问他。

 

   传递委托实例很有用,这样回调方法就可以使用它获得异步方法的结果。

   只要委托方法完成任务,就调用回调方法。不需要在主线程中等待结果。但是在委托线程的任务未完成之前,不能停止主线程,除非主线程结束时停止的委托线程没有问题

            dl.BeginInvoke(1,3000,TakesAWhileCompleted,dl);

            for(…..)

 {

       ….

  …

 }

 

 ..

 Static void TakesAWhileCompleted(IAsyncResult  ar)

 {

      …

 }

 

   使用的回调方法是从委托线程中调用,而不是从主线程中调用。

 

   除了定义一个单独的方法,并给它传递BeginInvike()方法之外,Lambda表达式也非常适合这种情况。参数ar是IAsyncResult类型。在实现代码中,不需要把一个值赋予BeginInvike()方法的最后ˉ个参数,因为Lambda表达式可以直接访问该作用域外部的变量d1。但是,Lambda表达式的实现代码仍是从委托线程中调用,以这种方式定义方法时,这不是很明显。

 

 

   异步委托不仅能用于委托,相同的编程模型(即异步模式)在NetFmmewOrk的各个地方都能见到。例如,可以用HupWebRequest类的BeginGetResponse异步发送HttpWeb请求,使用sqlCommand类的BeginExecuteReader()方法给数据库发送异步请求。这些参数类似于委托的BeginInvoke()方法的参数,也可以使用相同的方式获得结果。(这些内容后面会提到)

 

  (2)Thread类

   使用Thread类可以创建和控制线程。

    staticvoid Main(string[] args)

    {

      var t =newThread(ThreadMain);

        t.Start();

        Console.WriteLine("mainthread");

        Console.ReadKey();

    }

 

   staticvoid ThreadMain()

   {

       Console.WriteLine("Running inThread");

   }

 

   Thread类的构造函数重载为接受Theadstart和ParameterizedThreadStart类型的委托参数。Threadstart委托定义了一个返回类型为void的无参数方法。在创建了Thread对象后,就可以用Start()方法启动线程.

  

   给线程传递数据

   给线程传递一些数据可以采用两种方式。一种方式是使用带Parameterizedhreadstart委托参数的Thread构造函数,另一种方式是创建一个自定义类,把线程的方法定义为实例方法,这样就可以初始化实例的数据,之后启动线程。

 

   1.要给线程传递数据,需要某个存储数据的类或结构。这里定义了包含字符串的Data结构,但可以传递任意对象。

   public struct  Data

   {

      Publicstring Message;

   }

   如果使用了Parameterizedhreadstart委托,线程的入口点必须有一个object类型的参数,且返回类型为void。对象可以强制转换为任意数据类型.

   Static void ThreadMain(object o)

   {

     Data d = (Data)o;

     …

   }

   通过Thread类的构造函数,可以将新的入口点赋予ThreadMain,传递变量d,以此调用start( )方法。

   Static void Main()

   {

     Var d = new Data{Message = “infro”;

     Var t = new Thread(ThreadMain);

     T.Start(d);

   }

 

   2.给新线程传递数据的另一种方式是定义一个类(参见MyThread类),在其中定义需要的字段,将线程的主方法定义为类的一个实例方法:

 

   Public class MyThread

   {

   Privatestring data;

   Public  MyThread(string data)

   {

      This.data= data;

}

 

Public void ThreadMain()

{

    Console.writeLine(data);

}

   }

 

  Static void Main()

  {

Var d = new MyThread ( “infro”);

Var t = newThread(d.Thread.Main );

T.Start();

  }

      

 

 

   后台线程

   只要有一个前台线程在运行,应用程序的进程就在运行。如果多个前台线程在运行,而main()方法结束了,应用程序的进程就仍然是激活的,直到所有前台线程完成其任务为止。

 

   在默认情况下,用Thread类创建的线程是前台线程。线程池中的线程总是后台线程。

 

   在用Thread类创建线程时,可以设置IsBackground属性,以确定该线程是前台线程还是后台线程。

Main()方法将线程t1的IsBackgromd属性设置为false(默认值)。在启动新线程后,主线程就把结束消息写入控制台中。新线程会写入启动消息和结束消息,在这个过程中它要睡眠3秒。在新线程会完成其工作前,这3秒钟有利于主线程结束。

 

classProgram

     {

        static void Main(string[] args)

        {

            var t1 = new Thread(ThreadMain) { Name ="MyThread",IsBackground =false};

            t1.Start();

            Console.WriteLine("Main threadis ending");

        }

 

        static void ThreadMain()

        {

            Console.WriteLine("Thread {0}started",Thread.CurrentThread.Name);

            Thread.Sleep(3000);

            Console.WriteLine("Thread {0}completed",Thread.CurrentThread.Name);

            Console.ReadKey();

        }

}

 

   尽管主线程会提前完成其工作,但在启动应用程序时,会看到写入控制台的完成消息。原因是新线程也是一个前台线程。

 

   线程的优先级

 

   线程由操作系统调度。给线程指定优先级,就可以影响调度顺序。

 

   在改变优先级之前,必须理解线程调度器。操作系统根据优先级来调度线程。调度优先级最高的线程以在CPU上运行。

 

   线程如果在等待资源,它就会停止运行,并释放CPU。线程必须等待有几个原因,例如,响应睡眠指令、等待磁盘I/o的完成,等待网络包的到达等。

 

   如果线程不是主动释放CPU,线程调度器就会抢占该线程。如果线程有一个时间量,它就可以继续使用CPU。

 

   如果优先级相同的多个线程等待使用CPU,线程调度器就会使用一个循环调度规则,将CPU逐个交给线程使用。如果线程被其他线程抢占,它就会排在队列的最后:只有优先级相同的多个线程在运行,才用得上时间量和循环规则。

 

   优先级是动态的。如果线程是CPU密集型的(一直需要CPU,且不等待资阏,其优先级就低于用该线程定义的基本优先级。如果线程在等待资源,随着优先级的提高它的优先级就会增加。

 

   由于优先级的提高,线程才有可能在下次等待结束时获得CPU。

 

   在Thread类中,可以设置Priority属性,以影响线程的基本优先级。Priority属性需要ThreadPriority枚举定义的一个值。定义的级别有Hightest,AboveNormal,BelowNormal和Lowest.

 

 

 

   控制线程

 

   调用Thread对象的Start()方法,可以创建线程。但是,在调用Start()方法后,新线程仍不是处于Running状态,而是处于Unstarted状态。只要操作系统的线程调度器选择了要运行的线程,线程就会改为Runing状态。读取Thread.ThreadState属性,就可以获得线程的当前状态。

 

   使用Thread.Sleep()方法,会使线程处于WaitSleepJoin状态,在经历Sleep()方法定义的时间段后,线程就会等待再次被唤醒。

 

   要停止另一个线程,可以调用Thread.Abort()方法。调用这个方法时,会在接到终止命令的线程中抛出一 个ThreadAbortException类型的异常。用一个处理程序捕获这个异常,线程可以在结束前完成一些清理工作。

线程还可以在接收到调用Thread.RestAbort()方法的结果ThreadAbortException异常后继续运行。

如果线程没有重置终止,接收到终止请求的线程的状态就从AbortRequested改为Aborted。

如果需要等待线程的结束,就可以调用Thread.Join()方法。Thread.Join()方法会停止当前线程,并把它设置为waitSleepJoin状态,直到加入的线程完成为止。

 

(3)线程池

   创建线程需要时间。如果有不同的小任务要完成,就可以事先创建许多线程,· 在应完成这些任务时发出请求。这个线程数最好在需要更多的线程时增加,在需要释放资源时减少。

 

   不需要自己创建这样一个列表。该列表由ThreadPool类托管。这个类会在需要时增减池中线程的线程数,直到最大的线程数。

 

   池中的最大线程数是可配置的。在双核CPU中,默认设置为1023个工作线程和1000个I/o线程。也可以指定在创建线程池时应立即启动的最小线程数,以及线程池 中可用的最大线程数。

 

   如果有更多的作业要处理,线程池中线程的个数也到了极限,最新的作业就要排队,且必须等待线程完成其任务。

 

   示例应用程序首先要读取工作线程和I/o线程的最大线程数,把这些信息写入控制台中。

接着在for循环中,调用ThreadPool.QueueuserWorkTtem()方法,传递一个WaitCallBack类型的委托,把JobForATheadO方法赋予线程池中的线程。线程池收到这个请求后,就会从池中选择一个线程,来调用该方法。如果线程池还没有运行,就会创建一个线程池,并启动第一个线程。如果线程池己经在运行,且有一个空闲线程来完成该任务,就把该作业传递给这个线程。

 

   线程池使用起来很简单,但它有一些限制:

   1.线程池中的所有线程都是后台线程。如果进程的所有前台线程都结束了,所有的后台线程就会停止。不能把入池的线程改为前台线程。

 

   2.不能给入池的线程设置优先级或名称。

 

   3.对于COM对象,入池的所有线程都是多线程单元(MTA)线程。许多CoM对象都需要单线程单元线程。

 

   4.入池的线程只能用于时间较短的任务。如果线程要一直运行(如Word的拼写检查器线程),就应使用Thread类创建一个线程。

 


1 0