电脑桌面
添加蚂蚁七词文库到电脑桌面
安装后可以在桌面快捷访问

关于同步方法里面调用异步方法的探究

来源:金蝶云社区作者:金蝶2024-09-164

关于同步方法里面调用异步方法的探究

关于同步方法里面调用异步方法的探究

前言

我在写代码的时候(.net core)有时候会碰到void方法里,调用async方法并且Wait,而且我还看到别人这么写了。而且我这么写的时候,编译器没有提示任何警告。但是看了dudu的文章:一码阻塞,万码等待:ASP.NET Core 同步方法调用异步方法“死锁”的真相 了解了,这样写是有问题的。但是为什么会有问题呢?我又阅读了dudu文章里提到的一篇博文:.NET Threadpool starvation, and how queuing makes it worse 加上自己亲手实验,写下自己的理解,算是对dudu博文的一个补充和丰富吧。

同步方法里调用异步方法

同步方法里调用异步方法,一种是wait() 一种是不wait()

void fun(){  
    funAsync.Wait();
    funAsync();
}

这两种场景都没有编译错误。
首先我们来看一下,在 void里调用 async 方法,并且要等待async的结果出来之后,才能进行后续的操作。

using System;using System.Threading;using System.Threading.Tasks;namespace ConsoleTool2{    class Program
    {        static void Main(string[] args)
        {
            Producer();
        }        static void Producer()
        {            var result = Process().Result;            //或者
            //Process().Wait();
        }        static async Task<bool> Process()
        {            await Task.Run(() =>
            {
                Thread.Sleep(1000);
            });

            Console.WriteLine("Ended - " + DateTime.Now.ToLongTimeString());            return true;
        }
    }
}

咱们看这个Producer,这是一个void方法,里面调用了异步方法Process(),其中Process()是一个执行1秒的异步方法,调用的方式是Process().Result 或者Process().Wait()。咱们来运行一遍。

没有任何问题。看起来,这样写完全没有问题啊,不报错,运行也是正常的。
接下来,我们修改一下代码,让代码更加接近生产环境的状态。

using System;using System.Threading;using System.Threading.Tasks;namespace ConsoleTool2{    class Program
    {        static void Main(string[] args)
        {            while (true)
            {
                Task.Run(Producer);
                Thread.Sleep(200);
            }
        }        static void Producer()
        {            var result = Process().Result;
        }        static async Task<bool> Process()
        {            await Task.Run(() =>
            {
                Thread.Sleep(1000);
            });

            Console.WriteLine("Ended - " + DateTime.Now.ToLongTimeString());            return true;
        }
    }
}

我们在Main函数里加了for循环,并且1秒钟执行5次Producer(),使用Task.Run(),1秒钟有5个Task产生。相当于生产环境的qps=5。
接下来我们再执行下,看看结果:

在第一秒里只执行了两次Task,就卡住了。我们再看下进程信息:

没有CPU消耗,但是线程数一直增加,直到突破一台电脑的最大线程数,导致服务器宕机。
这明显出现问题了,线程肯定发生了死锁,而且还在不断产生新的线程。
至于为什么只执行了两次Task,我们可以猜测是因为程序中初始的TreadPool 中只有两个线程,所以执行了两次Task,然后就发生了死锁。

现在我们定义一个Produce2() 这是一个正常的方法,异步函数调用异步函数。

 static async Task Producer2()
        {            await Process();
        }

我们再Main函数的循环里,执行Producer2() ,执行信息如下:

仔细观察这个图,我们发现第一秒执行了一个Task,第二秒执行了三个Task,从第三秒开始,就稳定执行了4-5次Task,这里的时间统计不是很精确,但是可以肯定从某个时间开始,程序达到了预期效果,TreadPool中的线程每秒中都能稳定的完成任务。而且我们还能观察到,在最开始,程序是反应很慢的,那个时候线程不够用,同时应该在申请新的线程,直到后来线程足够处理这样的情况了。咱们再看看这个时候的进程信息:

线程数一直稳定在25个,也就是说25个线程就能满足这个程序的运行了。
到此我们可以证明,在同步方法里调用异步方法确实是不安全的,尤其在并发量很高的情况下。

探究原因

我们再深层次讨论下为什么同步方法里调用异步方法会卡死,而异步方法调用异步方法则很安全呢?

咱们回到一开始的代码里,我们加上一个初始化线程数量的代码,看看这样是否还是会出现卡死的状况。
由于前面的分析我们知道,这个程序在一秒中并行执行5个Task,每个Task里面也就是Producer 都会执行一个Processer 异步方法,所以粗略估计需要10个线程。于是我们就初始化线程数为10个。

using System;using System.Threading;using System.Threading.Tasks;namespace ConsoleTool2{    class Program
    {        static void Main(string[] args)
        {
            ThreadPool.SetMinThreads(10, 10);           
            while (true)
            {
                Task.Run(Producer2);
                Thread.Sleep(200);
            }
        }        static void Producer()
        {            var result = Process().Result;
        }        static async Task Producer2()
        {            await Process();
        }        static async Tas

关于同步方法里面调用异步方法的探究

关于同步方法里面调用异步方法的探究前言我在写代码的时候(.net core)有时候会碰到void方法里,调用async方法并且Wait,而且我还看到别...
点击下载文档文档为doc格式

声明:除非特别标注,否则均为本站原创文章,转载时请以链接形式注明文章出处。如若本站内容侵犯了原著者的合法权益,可联系本站删除。

已经是第一篇
确认删除?
回到顶部
客服QQ
  • 客服QQ点击这里给我发消息
QQ群
  • 答案:my7c点击这里加入QQ群
支持邮箱
微信
  • 微信