C#多线程编程-必知必会

本文探讨了技术人成长中的核心能力——发现问题与运用技术解决问题,着重讲解了C#多线程编程的注意事项,包括控制并发数、共享资源管理、加锁与子线程异常处理,以及任务取消技巧。通过实例演示和实际场景分析,帮助开发者优化线程性能与避免常见错误。
摘要由CSDN通过智能技术生成

发现问题的能力,运用技术解决问题的能力,是一个技术人成长的关键

9963962449f868e4938bf2adbdde9931.jpeg

@图片故事:洋姜的花,拍摄于2022年7月23日,地点:北京奥林匹克森林公园 ,摄影师:刘先生

概要:使用C#发起多线程任务十分简单,本文旨在汇总多线程编程的注意事项,重点不在于如何发起多线程,主要内容如下:

  1. 控制线程并发数量

  2. 界定共享资源

  3. 加锁并控制锁范围

  4. 子线程异常处理

  5. 未完成任务取消

    希望对小伙伴儿们有所帮助

01


控制线程并发数量

多线程以多任务并行的方式,加快业务处理速度,但如果线程数量超出了系统的承载能力,反倒会造成系统整体性能下降,如何合理地控制线程并发数量,是多线程开发的关键。

推荐采用信号量机制,可以在线程总数未知的情况下,有效地控制并发线程数量,并且以瀑布流的形式,连续执行后续线程,逻辑清晰可控,执行性能高效。

基础代码逻辑如下:

//semaphoreCount是设定的可并行运行的最大线程数量
//taskCount是需要发起的线程的数量
using (Semaphore semaphore = new Semaphore(semaphoreCount, semaphoreCount))
{
    var woker = new Worker();
    Task[] tasks = new Task[taskCount];
    for (int step = 0; step < taskCount; step++)   
    {        
        //获取一个信号量,如果所有信号量都已使用,则等待直到一个被释放        
        semaphore.WaitOne();        
        //获得信号量之后,才能发起子线程        
        tasks[step] = Task.Factory.StartNew((data) => { woker.Work(data); }, innerData)
                                  .ContinueWith((task) =>                                  
                                  {                                      
                                      //线程完成,释放信号量                                      
                                      semaphore.Release();                                  
                                  });    
     }    
     //...
}

简单来说,是由于分时操作系统,多任务之间存在线程上下文切换,有兴趣的同学可以尝试一下,一次性启动2000个以上线程,查看计算机的资源耗用情况,以便有更真切的体会。

02


界定共享资源

线程共享资源,一类是业务本身需要多个子线程共同处理的资源,另一类是从性能角度考虑,需要被多个子线程共享的资源。

以数据查询为例,数据库连接是一种昂贵的资源,如果每个子线程单独创建数据库连接,必然会造成浪费,多个线程共用一个数据库连接是更合理的选择,因此,数据库连接便是共享资源。

有兴趣的同学可以测试一下,同时启动50个以上线程,如果每个线程创建一个数据库连接,会造成数据库短时间内无法创建足够连接而报错。

03


加锁并控制锁范围

对共享资源进行访问时,需要加锁保护,防止并发错误。

对于业务本身处理的共享资源,加锁主要是防止数据处理错误;对于集合类型的共享资源,建议首选System.Collections.Concurrent 命名空间下的集合类型,以达到线程安全的目的;对于如数据库连接之类的资源,加锁是为了防止程序异常,如数据库连接、HttpClient对象,在一个请求处理完之前,是不能被其他线程访问的,因此需要加锁,确保串行访问是必须的。

对于锁对象,推荐的写法如下,至于是不是要加static ,要看具体业务场景,静态变量的作用域是整个应用程序,如果有两个以上请求同时到达,那么在访问到加锁代码块时,请求也是串行执行的,普通变量的作用域是当前对象,锁范围也是在当前对象内,请求间相互不影响。

readonly object locker = new object();

04


子线程异常处理

概括成一句话是:在明确异常处理要做什么的情况下,才进行异常处理,否则,让异常抛出,交由外层程序处理即可。参考我上一篇文章:异常处理,究竟是处理什么

多线程下异常处理的不同之处在于:子线程内的异常,不会直接抛出到主线程,而是保存在了Task对象的Exception属性中。因此,需要开发小伙伴判断线程状态,进行异常处理。

基础代码逻辑如下:

Task.Factory.StartNew((data) => { woker.Work(data); }, innerData)            
            .ContinueWith((task) =>            
            {                
              //判断线程处理状态,如果执行失败,则抛出异常                
              if (task.Status == TaskStatus.Faulted)                
              {                    
                  throw task.Exception;                
              }      
            });

05


未完成任务取消

当某个子线程发生异常之后,取消后续相关线程的执行,符合绝大多数业务逻辑。

取消线程操作需要用到 CancellationTokenSource 类,线程启动时,注册“取消凭证(Token)”,当某个子线程发生异常后,调用CancellationTokenSource的Cancel()方法,通知相关线程取消操作。以后会写一篇CancellationToken的详细介绍。

基础代码逻辑如下:

//声明 CancellationTokenSource
using (CancellationTokenSource cancellation = new CancellationTokenSource())
{
    Task[] tasks = new Task[taskCount];
    for (int step = 0; step < steps; step++)
    {
        semaphore.WaitOne();
        //注册cancellation.Token
        tasks[step] = Task.Factory.StartNew((data) => { woker.Work(data); }, innerData, cancellation.Token)
                                .ContinueWith((task) =>
                                {
                                    if (task.Status == TaskStatus.Faulted)
                                    {
                                        //通知取消任务
                                        cancellation.Cancel(true);
                                        throw task.Exception;
                                    }
                                    semaphore.Release();
                                });
    }
}

有多线程开发经历的小伙伴,可以看一下自己的代码,是否有对以上几点的处理。以上内容均来自于我个人的经验总结,如有疏漏,欢迎小伙伴补充指正。

最后,说一下对于多线程的认识,了解二次元的小伙伴应该知道一个词:“结界”,线程与结界有很多相似之处,一个子线程就相当于一个结界,结界内外虽处于同一空间,但却属于不同的世界,结界阻断了结界内外的联系,但又可以相互作用,更多相似处,小伙伴们自己体会。


您的反馈是我坚持的动力,欢迎点赞,转发,关注

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值