c# 多线程入门记录

c# 多线程入门记录

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

  • 多线程的开启方式

    命名空间:using System.Threading;

    • 委托方式开启

    调用一个委托的BeginInvoke方法可以开启一个线程

    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

    • 对象方法开启

    实例化一个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的输出顺序,每次运行都会不同。此外线程池中的线程不能改变其优先级、也不能改变它的前后台状态。线程池中的线程 都是 后台线程。尽量在工作量小的线程才去使用线程池。

  • 任务

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

    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 中的参数必须是引用类型。

注意避免 死锁

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

上述是一段 可能发生死锁的伪代码。 即当线程y 将b加锁后,且线程x将a加锁后,即死锁。两线程都无法等待到第二个加锁数据的自由状态。将x、y中的a、b加锁顺序保持一致,则可以避免该种情况。

转载于:https://www.cnblogs.com/EffectL/p/6846642.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值