C# 语法 の 异步



1.什么是线程?

  • 每个线程都是在进程内执行。
  • 每个线程都是独立的。
  • 两个线程交叉点的那个数据,该数据被称为是数据共享状态。
  • 当一个线程与另一个线程上代码的执行交织的那一点,就被称为线程被抢占了。

看下边的例子:

 class Program
    {
        static void Main(string[] args)
        {
            Thread t = new Thread(WriteY);
            t.Name = " cw Y";
            t.Start();

            for (int i = 0; i < 1000; i++)
            {
                Console.Write("X");
            }
            
        }
        static void WriteY()
        {
            for (int i = 0; i < 1000; i++)
            {
                Console.Write("Y");
            }
        }
    }

最后输出的结果:
在这里插入图片描述

注意:

  • 在单核计算机上,操作系统为每个线程来分配 “时间片” 来模拟并发 。
  • 在多核处理器上,两个线程才是真正的并行执行 。

注意线程的几个属性

  • 线程一旦开始执行,IsAlive 就是 true ,线程结束就是False 。
  • 线程结束的条件是:线程构造函数出入的委托执行完了。
  • 线程一旦结束,就无法再重启。
  • 每个线程都有一个 Name 属性,通常用于调试。且 Name 属性只能设置一次,以后再更改就会抛出异常。
  • 静态的Thread.CurrentThread 属性,会返回当前执行的线程。

2.Joint属性

1. t.join 即 要等 t 这个线程执行完以后,再执行下边的线程;

代码如下:

  class Program
    {
        static void Main(string[] args)
        {
            Thread.CurrentThread.Name = "Main thread ";
            Thread t = new Thread(WriteY);
            t.Name = "Y Thread";
            t.Start();
            t.Join();
            for (int i = 0; i < 1000; i++)
            {
                Console.Write("X");
            }
            
        }
        static void WriteY()
        {
            for (int i = 0; i < 1000; i++)
            {
                Console.Write("Y");
        } }  }

输出如下:
在这里插入图片描述

2.有两个方法,threat1, threat2。。如果在 Thread1 方法里面添加 Thread2.Join ()。那么就要等 Thread2方法执行完以后,再执行Thread1 。 Thread.Sleep(2000).是指当前的线程等待两秒。

在主线程里面调用 t.join() 这个方法,那么主线程就要等t线程进行完以后再进行继续进行。

具体看代码:

  class Program
    {
        static Thread thread1, thread2;
        static void Main(string[] args)
        {
              thread1 = new Thread(ThreadProc);
              thread1.Name = "Thread1";
              thread1.Start();

            thread2 = new Thread(ThreadProc);
            thread2.Name = "Thread2";
            thread2.Start();
        }
        private static void ThreadProc()
        {
            Console.WriteLine($"Current thread: {Thread.CurrentThread.Name}");
            if (Thread.CurrentThread.Name=="Thread1"
                &&thread2.ThreadState!=ThreadState.Unstarted)
            {
                thread2.Join();
            }
            Thread.Sleep(2000);
            Console.WriteLine($"当前线程名{Thread.CurrentThread.Name}");
            Console.WriteLine($"thread1 的状态{thread1.ThreadState}");
            Console.WriteLine($"thread2 的状态{thread2.ThreadState}");

        }
    }

输出结果如下:(思考背后发生的流程)
在这里插入图片描述

3.Join()后边可以添加一个 超时参数 用毫秒或者 TimeSpan 都可以

如果返回值true 说明是线程结束了,如果是false,说明超时了;

下边相对于上边来讲,就是如果说这个线程超过2s了,就走上边 if 语句,如果没有超过两秒,就走else 语句。

 class Program
    {
        static Thread thread1, thread2;
        static void Main(string[] args)
        {
              thread1 = new Thread(ThreadProc);
              thread1.Name = "Thread1";
              thread1.Start();

            thread2 = new Thread(ThreadProc);
            thread2.Name = "Thread2";
            thread2.Start();
        }
        private static void ThreadProc()
        {
            Console.WriteLine($"Current thread: {Thread.CurrentThread.Name}");
            if (Thread.CurrentThread.Name=="Thread1"
                &&thread2.ThreadState!=ThreadState.Unstarted)

                //意思就是等他两秒,如果两秒之内结束了,就走第一行,
                //如果两秒之内没有结束,就走第二行。
                if (thread2.Join(2000))
                {
                    Console.WriteLine("两秒之内结束了");
                }
                else
                {
                    Console.WriteLine("两秒没有结束");
                }
            Thread.Sleep(2000);
            Console.WriteLine($"当前线程名{Thread.CurrentThread.Name}");
            Console.WriteLine($"thread1 的状态{thread1.ThreadState}");
            Console.WriteLine($"thread2 的状态{thread2.ThreadState}");

        }
    }

4.阻塞

如果线程的执行由于某些原因导致暂停,那么就认为该线程被阻塞了

例如:使用 sleep 方法和 Join 方法等待其他线程结束。

解除阻塞的方法

  • 阻塞条件被满足
  • 操作超时(如果设置超时的话)
  • 通过 Thread.Interrupt() 进行打断
  • 通过 Thread.Abort() 进行中止

5.本地 VS 共享状态

本地独立

CLR 为每个线程分配自己的内存栈(stack),以便使本地变量保持独立

在这里插入图片描述

Shared 共享

如果多个线程都引用了同一个对象实例,那么他们就共享了数据。

在这里插入图片描述
如果更改了线程的等待时间,那么打印 Down 的次数就是不确定的。所以说他是不安全的

为了避免这种情况,可以使用锁定和线程安全

在读取和写入共享数据的时候,可以通过使用互斥锁(exclusive lock),就可以修复前边例子的问题。

C#中使用 lock 语句来加锁。当两个线程同时竞争一个锁的时候(锁可以基于任何引用对象),一个线程会阻塞或等待,直到锁编程可用状态。

 class Program
    {
        static bool down;
        static readonly object _locker = new object();
        static void Main(string[] args)
        {
            new Thread(Go).Start();
            Go();

        }
        static void Go()
        {
            lock (_locker)
            {
                if (!down)
                {
                    Console.WriteLine("DOWN");
                    down = true;
         } } } }

lock 里面的内容,在同一个时间内,只能被一个线程调用


6.如何往线程传参

往线程里传参,最简单的方法是使用 lambda 表达式

 static void Main(string[] args)
        {
            Thread t = new Thread(()=>PrintAA("你好","世界"));
            t.Start();
        }
        static void PrintAA(string message,string aa)
        {
            Console.WriteLine(message+aa);
        }

当然,也可以直接把所有的逻辑都写在 lambda 里面

在这里插入图片描述

7.多线程中的异常处理

在创建线程时,在作用范围内的 try / catch / finally 块,在线程开始执行后就与线程无关了

在这里插入图片描述

8.富客户端应用程序的线程

  • 在 WPF , UWP , WinForm 等类型的程序中,如果主线程在执行耗时的操作,就会导致整个程序没有响应。
  • 针对这种耗时的操作,一种流行的做法就是启动一个 worker 线程。

9.线程池

  • 当开始一个线程的时候,将花费几百微妙来组织类似以下的内容: 一个新的局部变量栈。
  • 线程池可以节省这种开销: 通过预先创建一个可循环使用线程的池来减少这一开销。
  • 线程池对于高效的并行编程和细颗粒度并发是必不可少的
  • 线程池允许在不被线程启动的开销淹没的情况下,进行短期操作。(此处的短期操作是指运行时间还没有线程开启的时间长)

使用线程池需要注意的几点

  • 不可以设置线程的 Name
  • 池线程都是后台线程
  • 阻塞线程可能使性能降低
  • 可以自由的更改池线程的优先级
  • 可以通过 Thread.CurrentThread.IsThreadPoolThread 属性来判断是否执行在池线程上。

显示的在池线程运行代码的方式就是使用 Task.Run


线程池中的整洁

  • 线性池能确保临时超出 计算-Bound 的工作不会导致CPU超额定语
  • CPU超额订阅:活跃的线程超过 CPU 核数,操作系统就需要对线程进行时间切片
  • 超额订阅对性能影响很大,时间切片需要昂贵的上下文切换,并且可能使 CPU 缓存失效,而 CPU 缓存对于现在处理器性能至关重要

10.Task

1.使用 Thread 来处理,和创建并发的一种低级别的工具,他有一些限制:

  • 虽然可以很方便的传入数据,但是当 Join 的时候,很难从线程获得返回值(可能需要设置一些共享字段 )
  • 如果抛出异常,捕获和传播异常都很麻烦
  • 很难使用较小的并发来组建大型的并发

Task 可以很好的解决上述问题,
他是一个相对高级的抽象

开启一个Task 最简单的方法就是使用 Task.Run ( )

代码如下:(传入一个委托即可)

 static void Main(string[] args)
        {
            Task.Run(() => Console.WriteLine("你好"));
        }

但是运行,却没有输出,这是为什么呢?

Task 默认使用线程池,也就是后台线程,当主线程结束时,你说创建的所有 task 都会结束

这个Task还没运行完,主线程就已经运行完了,所以这种情况下,Task就默认不再执行了,这种情况下,可以让主线程 “阻塞”

 static void Main(string[] args)
        {
            Task.Run(() => Console.WriteLine("你好"));
            Console.ReadLine();
        }
  • Task.Run 返回一个 Task 对象,可以使用它来监视其过程,在 Task.Run 之后,我们没有调用Start ,因为该方法创建的是 “热” 任务(hot task)
  • 可以通过Task的构造函数创建 “冷” 任务(cold task),但是实际上很少这么做
  • 可以使用 Task 的 Status 属性来跟踪 task 的执行状态

wait 等待

调用task 的Wait方法会进行阻塞,直到操作完成,相当于调用了 thread 上的Join 方法。

看代码:

  static void Main(string[] args)
        {
            Task task = Task.Run(()=>
            {
                Thread.Sleep(3000);
                Console.WriteLine("FOO");
            }
                );
            Console.WriteLine(task.IsCompleted);
            task.Wait();
            Console.WriteLine(task.IsCompleted);
        }

执行结果:
在这里插入图片描述

Task的返回值

  • Task 有一个泛型子类叫做 Task<TResult>,他允许发出一个返回值
  • 使用Func<TResult> 委托或者兼容的Lambda 表达式来调用 Task.Run 就可以得到Task <TResult>。
  • 随后,可以使用Result 属性来获得返回的结果。如果 task 还没完成操作,访问 Result 属性会阻塞该线程,直到该 task 完成操作。

代码:

static void Main(string[] args)
        {
            Task<int> task = Task.Run(()=>
                {
                    Console.WriteLine("foo");
                    return 3;
                });

            int result = task.Result;
            // 如果此时 Task 没有完成,那么就会阻塞
            Console.WriteLine(result);
        }

在这里插入图片描述

Task 的异常

与 Thread 不一样, Task 可以很方便的传播异常。
如果你的 task 里面抛出了一个未处理的异常(故障),那么该异常就会重新被抛给

  • 调用了 wait() 的地方
  • 访问了 Task <TResult> 的 Result 属性的地方

代码例子

注意是哪里抛出了异常
is 是判断是否兼容,是的话就是 true 错的话就是 false
CLR 将异常包包裹在 AggregateException 里,以便在并行编程场景中发挥很好的作用。

 static void Main(string[] args)
        {
            Task task = Task.Run(() => { throw null; }) ;
            try
            {
                //异常会被抛送到有 wait 的地方
                task.Wait();
            }
            catch (AggregateException aex)
            {
                // 看 A 是否是 B ,如果不是,抛出异常。
                if (aex.InnerException is NullReferenceException)
                {
                    Console.WriteLine("null");
                }
                else
                {
                    throw;
          } } }


参考资料

【1】is as 的区别

【2】https://www.bilibili.com/video/BV1Zf4y117fs/?p=16

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值