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加锁顺序保持一致,则可以避免该种情况。
posted @ 2017-05-12 17:49  EffectL  阅读(244)  评论(0编辑  收藏  举报