zoukankan      html  css  js  c++  java
  • c# 多线程入门记录

    c# 多线程入门记录

    记录下c#多线程的常用使用方法。
    基础:一个进程可以包含多个线程,把进程比作工厂,用来完成某个业务,那么线程相当于该工厂的车间。多个车间相互合作完成各自的任务。使得工厂完成它的业务。而车间与车间是共享使用一个工厂的资源的如电力、人力、资金等。

    • 多线程的开启方式

    命名空间:using System.Threading;

    - <h2>委托方式开启</h2>
    
    调用一个委托的BeginInvoke方法可以开启一个线程
    
    ```c#```
    static void TestMethod(){
    	Console.WriteLine("测试线程");
    }
    
    static void Main(string []args){
    	Action a=TestMethod;
    	IAsyncResult ar= a.BeginInvoke(null,null);
    	//通过ar可以对该线程进行操作,具体详细方法可查msdn:IAsyncResult。
    	Console.ReadKey();
    }
    ```
    注意:
    BeginInvoke的两参数。第一个是一个委托参数M,第二个是Object参数O。 M是回调委托,当ar执行完毕后会调用M,而O是这个M的参数。
    
    	```c#```
    //这里演示,当函数有参数和返回值时的情况
    static int TestMethod(string str){
    	Console.WriteLine("测试线程"+str);
    	Thread.Sleep(1000); 
    	//Thread的静态方法,可暂停当前线程x ms。
    	Console.WriteLine("测试结束");
    	return 100;
    }
    
    static void Main(string []args){
    	Func<string,int> a=TestMethod;
    	IAsyncResult ar= a.BeginInvoke(null,null);
    	if(ar.AsyncWaitHandle.WaitOne(2000)){
    		var res=ar.EndInvoke(ar);
    		Console.WriteLine(res);
    	}
    	Console.WriteLine("main");
    	//这里用于等待线程执行完毕。2000代表最长等待时间 单位是毫秒。
    	Console.ReadKey();
    }
    ```
    输出:
    测试线程
    测试结束
    100
    main
    
    用回调方式也可检测结束,以下为回调方式的main函数
    ```c#```
    	static void Main(string []args){
    	Func<string,int> a=TestMethod;
    	a.BeginInvoke(ar=>{
    		Console.WriteLine(a.Envoke(ar));
    	},null)
    	Console.WriteLine("main");
    	Console.ReadKey();
    }
    ```
    输出:
    main
    测试线程
    测试结束
    100
    
    - <h2>对象方法开启</h2>
    
    实例化一个Tread对象,也可以开启一个线程。
    	```c#```
    	static void TestMethod(object o){
    	Console.WriteLine("测试线程"+o);
    	Thread.Sleep(1000); 
    	//Thread的静态方法,可暂停当前线程x ms。
    	Console.WriteLine("测试结束");
    }
    	static void Main(string []args){
    	Thread t=new Thread(TestMethod);
    	t.Start("Test");
    	Console.WriteLine("main");
    	Console.ReadKey();
    }
    ```
    输出:
    main
    测试线程Test
    测试结束
    
    注意:
    采用这种方式,函数不能有返回值,参数必须为Object型。Thread有四种构造方式,其余种类可查Msdn。当然该种方法,也可以将线程执行单独写一个类,执行某个对象的成员方法。返回值的问题很容易就解决了。
    Thread 对象提供多种对线程的操作方式。如Start、Abort、Join等,这里不再多加介绍,可自行查找。
    
    • 多线程前台后台概念以及优先级

      1. 前后台
        c#中 线程分为前台线程和后台线程。
        当前台线程执行完毕后,将终止所有的后台线程。一个进程只有所有的前台线程执行完毕,才算进程执行完毕。
        使用new方法 如Thread t= new Thread(delegete), 声明的线程,默认为 前台线程,可以通过 t.IsBackGround=true;的方式设置为后台线程。
      2. 优先级
        优先级是每一个线程带有的一个属性,CPU在一个时间中 只能执行一个线程。而线程优先级,会使线程调度器判断 什么线程优先执行。
    • 线程池

      类似对象池的一个东西,C# 预定义了一个ThreadPool类,用户可以想线程池中申请一个新线程来完成某项工作。也是一种开启线程的方式。具体操作例子如下: ```c#``` static void TestMethod(object o){ Console.WriteLine("测试线程"+o); Thread.Sleep(1000); //Thread的静态方法,可暂停当前线程x ms。 Console.WriteLine("测试结束"); } static void Main(string []args){ ThreadPool.QueueUserWorkItem(TestMethod,"T1"); ThreadPool.QueueUserWorkItem(TestMethod,"T2"); ThreadPool.QueueUserWorkItem(TestMethod,"T3") ThreadPool.QueueUserWorkItem(TestMethod,"T4") Console.ReadKey(); } ``` 输出: 测试线程T3 测试线程T1 测试线程T2 测试线程T4 测试结束 测试结束 测试结束 测试结束

      注意:
      线程池开启线程的 顺序不是确定的,所以T1、2、3、4的输出顺序,每次运行都会不同。此外线程池中的线程不能改变其优先级、也不能改变它的前后台状态。线程池中的线程 都是 后台线程。尽量在工作量小的线程才去使用线程池。

    • 任务

      任务是线程的一种封装,任务也是一种开启线程的方式。

      c#
      static void TestMethod(object o){
      Console.WriteLine("测试线程"+o);
      Thread.Sleep(1000);
      //Thread的静态方法,可暂停当前线程x ms。
      Console.WriteLine("测试结束");
      }
      static void Main(string []args){
      Task t = new Task(Th, "Test");
      t.Start();
      Console.ReadKey();
      }

      输出:
      测试线程Test
      测试结束
      
      或者:
      ```c#```
      static void TestMethod(object o){
      	Console.WriteLine("测试线程"+o);
      	Thread.Sleep(1000); 
      	//Thread的静态方法,可暂停当前线程x ms。
      	Console.WriteLine("测试结束");
      }
      static void Main(string []args){
      	var tf= new TaskFactory();
      	tf.StartNew(Th, "Test")
      	Console.ReadKey();
      }
      

      输出结果 与上述一至

      最常见的TaskFactory的用法:
      C#
      static void Th(object o)
      {
      Console.WriteLine("TH"+o);
      Console.WriteLine("ID:" + Task.CurrentId);
      Thread.Sleep(1000);
      Console.WriteLine("TH");
      }
      static void Sh(Task t,object o)
      {
      Console.WriteLine("Sh" + o);
      Console.WriteLine("PreTaskId:" + t.Id);
      Console.WriteLine("ID:" + Task.CurrentId);
      Thread.Sleep(1000);
      Console.WriteLine("Sh");
      }
      static void Main(string []args){
      var tf = new TaskFactory();
      Task t= tf.StartNew(Th, "T1");
      Task t2 = t.ContinueWith(Sh,"S2");
      Console.ReadKey();
      }

      输出	:
      THT1
      ID:1
      TH
      ShS2
      PreTaskId:1
      ID:2
      Sh
      利用任务模式很容易可以控制线程的依赖关系。上述例子中t2就是t的一个子线程,只有当t执行完毕,t2才能开始执行。且利用TaskFactory产生的任务都有唯一不可改的ID值。便于操作,也可以与其他线程分隔开。非常好用。
      
      
    • 加锁

      当一个数据需要被多个线程使用时,可能会产生逻辑问题。
      如下例子:
      c#
      class Test{
      private int num;
      public Test(){num=1;}
      public Work(){
      while(true){
      num=1;
      if(num>1)
      Console.Write(num)
      num++;
      }
      }
      }
      static void Main(string []args){
      var test=new Test();
      var t1=new Task(test.work());
      t1.Start();
      //var t2=new Task(test.work());
      //t2.Start();
      }

      很明显当main函数中这样写时。代码正常运行。不会输出任何的东西。
      但是当把注释的代码取消注释后运行。发现可以循环输出num;
      这是因为。两个线程异步执行时,会出现两者中一个线程a执行到if(num>1)时,另外的一个线程b执行到了num++;造成num=2;进行输出的情况。
      但当我们加锁后,即可避免该种情况。
      
      将main函数代码做如下修改:
      ```c#```
      static void Main(string []args){
      	var test=new Test();
      	var t1=new Task(() => {
                  lock (t) //
                  {
                      t.work();
                  }
      		//判断t是否被锁,如果被锁则将在lock位置等待,直到t变自由,若为自由状态,则往下执行,并把该对象加锁。直到执行完毕,将t对象自由。
              });
      	t1.Start();
      	var t2=new Task(() => {
                  lock (t)
                  {
                      t.work();
                  }
              });
      	t2.Start();
      }
      

      注意:
      使用这种方法,即可确保t.work()在同一时刻,仅有一个线程进行访问。lock 中的参数必须是引用类型。

    注意避免 死锁

    c#
    static void x(){
    lock(a){
    lock(b){
    ....
    }
    }
    }
    static void y(){
    lock(b){
    lock(a){
    ....
    }
    }
    }

    上述是一段 可能发生死锁的伪代码。 即当线程y 将b加锁后,且线程x将a加锁后,即死锁。两线程都无法等待到第二个加锁数据的自由状态。将x、y中的a、b加锁顺序保持一致,则可以避免该种情况。
  • 相关阅读:
    jmeter脚本开发:插件安装和设计场景(五)
    jmeter脚本开发:SOAP接口和JDBC(四)
    jmeter脚本开发:控制器和参数化(三)
    gauge自动化框架踩坑(六):关于csv
    gauge自动化框架踩坑(五):关于表格
    gauge自动化框架踩坑(四):在测试报告中自定义messages
    MediaPlayer播放音频,也可以播放视频
    soundpool播放声音
    ContentProvider
    了解 IMyInterface.Stub
  • 原文地址:https://www.cnblogs.com/EffectL/p/6846642.html
Copyright © 2011-2022 走看看