C# NutShell 第十四章 并发与异步

线程

创建线程

1.线程时一个可以独立执行的执行路径

2.在同一个进程中多个线程会共享内存

3.在单核计算机中,操作系统会为每一个线程划分时间片来模拟并发执行

4.在多核心的计算机中,线程是真正的并行的

5.线程是抢占式的,需要和其他代码竞争

6.线程一旦启动,IsAlive属性就会返回true,直至线程停止。线程停止后就无法在启动了

7.设置线程的名称属性在调试时可以在调试栏看到

汇合与休眠

1.Join可以等待线程结束,只有线程结束后才往后面执行,同时可以给join设置超时时间

2.Sleep可以使线程暂停,此时的线程时阻塞的。Sleep(0)可以会使线程立即放弃自己的时间片

阻塞

1.阻塞的线程会立即交出自己的时间片,不再消耗处理器的时间

2.I/O密集:操作的大部分时间都在等待事情发生,一般涉及输入输出

3.计算密集:大量执行CPU操作

锁与线程安全

1.CLR为每一个线程分配了独立的内存栈,保证局部变量的隔离

2.当两个线程同时竞争一个锁时,另外一个线程会等待锁被释放,保证代码的安全性

向线程传递数据

用Lambda表达式

Thread t = new Thread(()=> 
{
    Go("sss");
});

使用传统方法

Thread t = new Thread(Go);
t.Start("sss");
//此时的m的类型必须是object
static void Go(object m)
{
    Console.WriteLine(m);
}

注意:不要在线程中引用同一块内存,应使用局部变量

异常处理

1.使用try/catch包住创建线程的代码是没用的,需要包住线程调用的方法

2.所有线程的入口都要添加异常出路

前台线程和后台线程

1.前台线程只要有一个在运行,则程序依然运行

2.当前台线程结束时,应用程序就停止了,后台线程也随之停止(不管运行否)

3.前台线程尽量等待后台线程的结束

线程的优先级

1.线程和进程都有优先级

2.提高线程的优先级就会减少其他线程的执行时间

3.如果想要提高线程的执行时间还需要提高整个进程的优先级

信号发送

一个线程接受其他线程的信号,以此来决定线程的执行与否

同步上下文

1.可以直接将UI的图形显示出来,但是复杂的数据需要更长时间才能显示

2.采用SynchronizationContext类可以达到次目标,该类可以捕获当前UI线程,在其他的线程中使用该类,可以将其他线程的数据滞后的发送到UI线程中

线程池

1.创建一个线程需要一定的耗时

2.线程池中的线程都是后台线程

3.阻塞线程池中的线程将影响性能

4.Task.Run就是在线程池中创建了一个线程池

5.线程池中线程的数量会根据当前执行的速度由操作系统自动调节


任务

Task不是一个线程,而是表示一个并发操作,而这个操作不一定要用线程来完成

启动任务

1.Task默认使用线程池中的线程,都是后台线程

2.Task.Run()表示热任务,可以直接运行任务,如果需要冷任务,则需要调用Task构造器

3.任务返回的Task类可以追踪当前任务的运行状态

4.Wait可以等待当前线程,同时也可以指定是将和取消令牌来提前取消等待

5.当需要运行长任务时,较好的方法是:

I/O密集型:TaskCompletionSource和异步函数通过回调函数

计算密集型:生产者/消费者队列

返回值

1.使用Func来代替Action就可以使Task有返回值

2.Task.Result可以查看Task的返回值,而该方法会阻塞当前线程

异常

1.如果Task出错了,那么在获取Result时,就会抛出异常,可以通过IsFaulted和IsCanceled来知道异常与否

2.后台程序会产生异常,最好在线程中处理这些异常。

3.有些异常有可能无法观测,有些异常会影响程序的正常运行。用TaskScheduler.UnobersvedTaskException来获取全局的未观测异常,记录日志。

延续

1.延续会告诉任务在完成之后继续执行后续的操作

2.延续通常由一个回调方法实现,该方法会在操作完成之后执行

            Task<int> a = Task.Run(() => 
            {
                Console.WriteLine("sb");
                return 1;
            });
            var awaiter = a.GetAwaiter();
            awaiter.OnCompleted(() =>
            {
                Console.WriteLine(awaiter.GetResult());
            });

3.OnCompleted表示在a线程运行完了之后立即执行

4.如果线程抛出异常,则在调用GetResult和Result时,都会重新抛出遗常

5.延续的代码和Task的代码是运行在同一个线程上的

TaskCompletionSource

1.TaskCompletionSource可以创建任务,这个任务是在操作结束或出错时手动创建附属任务

2.真正作用是创建一个不绑定线程的任务

Task.Delay

Task.Delay是Thread.Sleep的异步版本

Task.Delay不会阻塞当前线程,一般会使用await来等待Task.Delay完成


C#的异步函数

等待

1.async await会转化成GetAwaiter和OnCompleted

2.用async修饰会将await作为一个关键字而非标识符来避免二义性

3.async只支持返回类型为Task,Task<TResult>,void三种类型

步骤解析

1.在主线程中,如果没有碰到await,程序就会一直在主线程中。直到碰到await,就会开辟一个线程

2.此时,就有2部分代码,一部分是属于主线程的代码,一部分是分线程的代码。这时,两部分的代码会分别继续执行

3.最为关键的是,要分清楚那一部分为主线程代码,哪一部分为分线程代码。await前面的是主线程,await后面的是分线程。再强调一遍,开启分线程的分水岭是await标志,而不是async标志。也就是在async方法里面,await前面的代码属于主线程,await后面的代码才属于分线程

4.当主线程遇到awiat时,会立即撤出async方法,继续执行主函数代码。

5.分线程会执行剩余的代码。在分线程中调用的其他方法,也都是在分线程中执行的

6.在获取Task.Result时会强行等待

7.2个await就会开启2个线程

优化

1.同步完成,在async函数中不一定要调用await,可以返回同步代码。

2.在循环调用异步方法时,使用ConfigureAwait避免大量的消息回弹


异步模式

取消操作

1.取消令牌,CancellationToken可以取消异步操作

2.CancellationToken可以直接创建一个时间来决定何时取消异步,而不一定要手动执行

进度报告

1.通过向线程中添加一个Action委托来跟踪线程执行情况

2.可以使用IProgress来完成1中的情况

任务组合器

1.WhenAny:会在任务组中的任意一个任务完成时返回这个任务,返回结果是第一个完成的任务。

 var a = Task.WhenAny( Fun2(),Fun2());

返回结果为Task<Task<int>>或者Task<int>

可以将异步方法和延迟函数组合从而达到延时的目的.

2.WhenAll:当所有任务都完成时才完成

Task.WhenAll( Fun2(),Fun2());

返回结果是数组任务,即Task<int[]>或者int[]

3.自定义组合器

可以将WhenAny,WhenAll和延时,令牌取消函数组合来形成自定义组合

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值