.net core 中的同异步

同步&异步

同步顾名思义指的是同步进行,在单线程模式下按顺序执行。假如有十份工作需要完成,同步则相当于一个人干十份工作。异步则是多线程执行,相当于十个人干十份工作。

线程&关系

线程分为主线程,工作线程。主线程是进程的入口点创建的第一个线程。可以理解为树的干,工作线程是由主线程创建的线程,可以称作是树的枝。除此之外还分为前台线程和后台线程。前台线程能阻止应用程序的终结,只有所有的前台线程执行完毕,CLR才能关闭应用程序(即卸载承载的应用程序域)。后台线程则不会影响程序的关闭。程序关闭后,后台线程也会被释放掉。前台线程和后台线程都属于工作线程。
通俗一点,前台线程负责比较关键的任务,必须完成的。比如更新用户数据等,这类需求必须要等待线程完成,所以使用前台进程,后台线程负责一些无关任务,比如推送邮件之类的。

疑问

在开始本篇文章之前,其实我也是有很多疑问。根据疑问来找问题答案,会帮助你更了解问题的本身。

问题一:线程池

什么是线程池?

在讨论线程池之前,我们应该返回并描述什么是线程。线程是执行顺序程序所需的状态。大概是部分执行的方法的“调用堆栈”,包括每个方法的所有局部变量。关键是所有代码都需要线程才能“运行”。程序启动时,将被分配一个线程,多线程程序会创建其他线程,每个线程彼此同时执行代码。

在并发程度适中的世界中,线程是有意义的,每个线程都在执行复杂的操作。但是,某些工作负载具有完全相反的特征:发生了许多并发事件,并且每个工作都在做简单的事情。由于所有执行都需要一个线程,因此对于这种工作负载,重用该线程很有意义,因为它可以执行许多小的(不相关的)工作项。这是一个线程池。它有一个非常简单的API。在.NET中,ThreadPool.QueueUserWorkItem需要委托(方法)来运行。当您调用QueueUserWorkItem时,线程池将承诺运行将来传递的委托。(请注意,.NET不鼓励直接使用线程池。Task.Factory.StartNew(Action)还将方法排队到.NET中。

以上是microsoft 文档中的解释。对于线程池,我的理解是负责调度任务,优化线程处理。因为如果没有线程池,在执行高并发时会频繁的对线程创建销毁,这一部分造成了一定的资源浪费,线程池就是为了解决这个问题。.NET 为我们提供了线程池来帮助我们创建和管理线程。Task是默认会直接使用线程池,但是Thread不会。如果我们不使用Task,又想用线程池的话,需要使用ThreadPool类。

关于线程池,更多的部分需要自己去了解。

问题二:线程数

这个问题是非常关键的,因为这个性能关系十分密切。线程的多少决定了处理事件的效率,但是过多的线程又会造成不必要的开销,这是一个十分重要的问题点。我没有经过具体测试线程的数量关系,但是从网上找到了有关信息,有可能不是十分准确,仅供参考
最小线程池个数是CPU的核心数,最大线程池个数是32767。dotnet 生成新线程的规则是,当有新任务需要线程时,先在线程池中找空闲线程,如果没有空闲,就等待500ms看是否有线程空闲出来,如果还是没有,产生一个新线程。

问题三:阻塞

之所以想到这个问题是无意间看到了一篇关于为什么我的服务不能使所有内核饱和或似乎停滞的文档,文档链接。文章将原因指向了线程池,由于线程池里需要的线程过多造成了这个问题。大体原因是请求阻塞 --> 应用需求线程增加 --> 线程池增加线程 。由于并发原因,阻塞会叠加下去,所以线程池需要的线程数也会增加,这个原因使得很小的阻塞造成了极大的资源争取,后进来的请求像饥饿的孩子渴求食物一样等待空闲的线程。所以如何避免阻塞成了一个十分关键的问题。而前面所述文章也说明了阻塞的问题来源

  1. 调用任何执行I / O(因此可能会阻塞)但不是异步的API(由于它不是异步API,因此如果I / O无法快速完成,则必须阻塞)
  2. 调用Task.Wait()或Task.GetResult(调用Task.Wait)。使用这些API是危险信号。理想情况下,您处于异步方法中,而是使用“ await”。
    这两种情况的出现其实可以归结为一种,那就是多线程执行的任务,无法立即完成。

async & await关键字

在c# 中 ,async关键字用来指定某个方法、Lambda表达式或匿名方法自动以异步的方式来调用。async/await是用来进行异步调用的形式,内部其实还是采用线程池进行管理。
如果使用await,在调用方法时不会阻塞主方法线程,主方法线程会被释放,新的线程执行完成task后继续执行await后的代码减少线程切换开销,而之前的线程则空闲了。
为此我做了一个小的实验

public async Task<JsonResult> AsyncTest() {
            var info = string.Format("api执行线程:{0}", Thread.CurrentThread.ManagedThreadId);
            var starttime = DateTime.Now;
            int i = 0,max = 10000;
            while (i < max) {
                await GetString();
                i++;
            }
            var endtime = DateTime.Now;
            var t = endtime - starttime;
            //var t = await GetString();
            //var infoTaskFinished = string.Format("api执行线程(task调用完成后):{0}", Thread.CurrentThread.ManagedThreadId);
            return Json(new { timespan = t.TotalSeconds });//str = info ,str2 = t,str3 = infoTaskFinished });
        }
        public JsonResult NoAsyncTest() {
            var starttime = DateTime.Now;
            int i = 0, max = 10000;
            while (i < max)
            {
                GetString();
                i++;
            }
            var endtime = DateTime.Now;
            var t = endtime - starttime;
            return Json(new { str = t.TotalSeconds });
        }

        public async Task<string> GetString() {
            await Task.Delay(3);
            return string.Format("task 执行线程:{0}", Thread.CurrentThread.ManagedThreadId);
        }

以上是我在我的网站写的几个测试api。没有使用await关键字的返回时间是
在这里插入图片描述
使用await的反应时间是
在这里插入图片描述
这是因为不加await的不需要等待。相当于while的时间,添加了await中间加了些线程等待。这里再说一下为什么要使用await关键字。如前所述,不加wait,相当于不管这些线程的死活,线程干活的成功失败,都不用过问。但是这里还有一个点,这是非线程安全的,如果这些线程公用一个变量或者是读写操作,那么就会出异常。所以具体使用情况要看清楚。

public async Task<JsonResult> AsyncTest() {
            var info = string.Format("api执行线程:{0}", Thread.CurrentThread.ManagedThreadId);
            var starttime = DateTime.Now;
            int i = 0,max = 10000;
            while (i < max) {
                await Task.Factory.StartNew(()=> {
                    GetString();
                });
                
                i++;
            }
            var endtime = DateTime.Now;
            var t = endtime - starttime;
            //var t = await GetString();
            //var infoTaskFinished = string.Format("api执行线程(task调用完成后):{0}", Thread.CurrentThread.ManagedThreadId);
            return Json(new { timespan = t.TotalSeconds,num= num });//str = info ,str2 = t,str3 = infoTaskFinished });
        }
        
        public JsonResult NoAsyncTest() {
            var starttime = DateTime.Now;
            int i = 0, max = 10000;
            while (i < max)
            {
                Task.Factory.StartNew(() => {
                    GetString();
                });
                i++;
            }
            var endtime = DateTime.Now;
            var t = endtime - starttime;
            return Json(new { str = t.TotalSeconds ,num = num });
        }

        public string GetString() {
            //await Task.Delay(3);
            num += 1;
            return string.Format("task 执行线程:{0}", Thread.CurrentThread.ManagedThreadId);
        }

没有await的
在这里插入图片描述
含有await的
在这里插入图片描述
所以有关线程安全的地方要注意分别使用。还有一点是.wait()方法和await。两者都是等待线程结束,但是await不会造成阻塞。前面已经说过await的时候会释放主线程。

由于我的工作中很少遇到高并发的例子,这些也只是些浅显的理解,如果错误请在评论区指正。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值