首页 > 其他分享 >一个例子形象的理解同步与异步

一个例子形象的理解同步与异步

时间:2024-01-22 17:11:07浏览次数:20  
标签:异步 Reuest 同步 string int while 例子 Task true

请看一个示例:

同步方式请求接口

请求一次接口耗时大约100多毫秒

代码

一个for循环,循环500次,调用方法Reuest,Reuest方法中一个while(true)无限循环,同步方式请求url获取数据。
代码点评:要是写一个while(true)没问题,这是是想运行500个while(true),这代码是错误的,行不通。应该使用Thread或者Task.Run加TaskCreationOptions.LongRunning参数。
这当然是有问题的代码,请看下面运行截图,只有第一个while(true)在执行,其它的499个while(true)根本没有执行机会。

static int num = 0;
static ConcurrentDictionary<int, object> dict = new ConcurrentDictionary<int, object>();

static void Main(string[] args)
{
    CalcSpeed();

    for (int i = 0; i < 500; i++)
    {
        Reuest(i);
    }

    Console.WriteLine($"Main函数结束");
    Console.ReadLine();
}

static void Reuest(int index)
{
    dict.TryAdd(index, null);

    while (true)
    {
        string url = "http://localhost:5028/Test/TestGet";
        string result = HttpUtil.HttpGet(url);
        Interlocked.Increment(ref num);
    }
}

static void CalcSpeed()
{
    _ = Task.Factory.StartNew(() =>
    {
        Stopwatch sw = Stopwatch.StartNew();
        while (true)
        {
            Thread.Sleep(2000);
            double speed = num / sw.Elapsed.TotalSeconds;
            ThreadPool.GetMaxThreads(out int w1, out int c1);
            ThreadPool.GetAvailableThreads(out int w2, out int c2);
            Console.WriteLine($"有 {dict.Count.ToString().PadLeft(3)} 个 while(true) 在执行,线程池活动线程数:{(w1 - w2).ToString().PadRight(3)}  速度:{speed:#### ####.0} 次/秒");
        }
    }, TaskCreationOptions.LongRunning);
}

运行截图

说明

代码中没有创建线程,也没有使用Task.Run,请求一次接口耗时大约100多毫秒,while(true)在主线程中执行,平均1秒请求接口不到10次。
注意:只有第一个while(true)在执行。

修改1:在Reuest函数中添加一行代码Thread.Sleep(1);

代码

static void Reuest(int index)
{
    dict.TryAdd(index, null);

    while (true)
    {
        string url = "http://localhost:5028/Test/TestGet";
        string result = HttpUtil.HttpGet(url);
        Interlocked.Increment(ref num);

        Thread.Sleep(1);
    }
}

运行截图

说明

没什么用,速度还变慢了一点。
依然是有问题的代码。
依然只有第一个while(true)在执行。

修改2:在Reuest函数中添加一行代码await Task.Delay(1);

VS自动在void Reuest前面添加了async关键字

代码

static async void Reuest(int index)
{
    dict.TryAdd(index, null);

    while (true)
    {
        string url = "http://localhost:5028/Test/TestGet";
        string result = HttpUtil.HttpGet(url);
        Interlocked.Increment(ref num);

        await Task.Delay(1);
    }
}

运行截图

说明

速度快多了,并且越来越快。
有多个while(true)在执行,并且在执行的while(true)数量越来越多,最终会达到500个。
这是比较神奇的地方,仅仅加了一行await Task.Delay(1);同步方法Request就变成了异步方法。
在执行await Task.Delay(1);这一行时,其它while(true)得到了执行机会,你们可以验证一下。
同步请求分别在不同的线程中执行,你们可以打印线程ID验证一下。

修改3:前面使用的是HttpUtil.HttpGet同步请求,修改为异步请求,await Task.Delay(1);这一行也不需要了

代码

static async void Reuest(int index)
{
    dict.TryAdd(index, null);

    while (true)
    {
        string url = "http://localhost:5028/Test/TestGet";
        var httpClient = HttpClientFactory.GetClient();
        string result = await (await httpClient.GetAsync(url)).Content.ReadAsStringAsync();
        Interlocked.Increment(ref num);
    }
}

运行截图

说明

速度非常快。
异步的优势体现出来了。

修改4:有没有人会认为修改2,把同步代码用Task.Run包一下,速度会更快?

代码

static async void Reuest(int index)
{
    dict.TryAdd(index, null);

    while (true)
    {
        await Task.Run(() =>
        {
            string url = "http://localhost:5028/Test/TestGet";
            string result = HttpUtil.HttpGet(url);
            Interlocked.Increment(ref num);
        });

        await Task.Delay(1);
    }
}

运行截图

说明

线程饥饿,全部阻塞,没有返回结果,速度是0。

总结

通过这个例子形象地体会一下同步与异步,以及为什么要使用异步。
如果你写的代码是异步的,但是调用的IO接口又是同步的,这比真正的异步效率要差很多,但比同步代码有所提升。
针对修改2,有人会说,这代码有问题,后面的while(true)会延迟好久才会执行。但是如果for循环的数量是少量的,程序启动时的一点延迟是允许的,就没有问题,
修改代码如下:

for (int i = 0; i < 20; i++)
{
    Reuest(i);
}

运行截图:

说明:
20个while(true)都在运行,比一个while(true)要快很多。
当然,没必要这么写了,直接new 20个Thread就可以。
但如果for循环就是500次,而且需要调用的IO接口又是同步的,那么就老老实实写500个new Thread。
如果非要用异步,设置一下线程池的大小,大于500,避免线程饥饿。

ThreadPool.SetMinThreads(800, 800);
ThreadPool.SetMinThreads(600, 600);

标签:异步,Reuest,同步,string,int,while,例子,Task,true
From: https://www.cnblogs.com/s0611163/p/17979998

相关文章

  • mysql8.0主从不一致,重新同步从库
    背景:线上宕机,导致数据不一致,当时为了快速恢复业务,仅使用主库,现在需要恢复,因为主从数据相差比较大,所以对从库重新进行同步。1、首先重置从库的同步设置、并清除从库不一致数据1)#停止slavestopslave;#重置slave,会重置从库相关设置。resetslaveall;2)#清除已同步......
  • SQL Server 2012 AlwaysON 同步延迟时间
     SELECTavailability_mode_desc,role_desc,replica_server_name,last_redone_time,GETDATE()now,DATEDIFF(ms,last_redone_time,GETDATE())diffMSFROM((sys.availability_groupsASagJOINsys.......
  • Python web crawler(2)json异步加载的格式
    异步加载的特点点击“查看更多”等按钮,浏览器“刷新”按钮无反馈效果。查看浏览器点击F12的“DevTools”开发者工具,点选“网络”——“Fetch/XHR”——每点击一次“加载更多”就会出现一次网络请求点击刷新出来的“请求内容”——点击“响应”,可以看到响应的是“字典”点击标......
  • kafka入门(十):副本数据同步
    副本副本(Replica),指的是分布式系统对数据和服务提供的一种冗余方式。Kafka通过多副本机制实现故障自动转移,在Kafka集群中某个broker节点失效的情况下仍然保证服务可用。失效副本在ISR集合之外,也就是处于同步失效或功能失效(比如副本处于非存活状态)的副本统称为失效副本,失效副本......
  • 帮助编写异步代码的ESLint规则
    调试JavaScript中的异步代码有时就像在雷区中穿梭。你不知道console.log会在何时何地打印出来,也不知道代码是如何执行的。你很难正确构造异步代码,使其按照你的意图以正确的顺序执行。如果在编写异步代码时能得到一些指导,并在即将出错时收到一条有用的信息,那岂不更好?幸运的......
  • 线程异步操作
    目录什么是C++中的异步操作?std::async异步调用函数什么是C++中的异步操作?在C++中,异步操作是指在程序执行期间,可以同时执行多个任务,而无需等待前一个任务完成。这种并发执行的方式可以提高程序的性能和响应速度。C++中的异步操作通常通过多线程或异步任务来实现。使用std::asyn......
  • python 异步回调传递参数 warning cell-var-from-loop
    warning:cell-var-from-loopbing解释foriinrange(10):f=lambdai:iprint(f())Thewarningmessagecell-var-from-loopisemittedbyPylint,aPythoncodeanalysistool.Thiswarningisraisedwhenavariableisdefinedinsidealoopandused......
  • 使用 Canal 实时从 MySql 向其它库同步数据
    目前绝大多数项目还是采用mysql作为数据存储,对于用户访问量较高的网站来说,mysql读写性能有限,我们通常会把mysql中的数据实时同步到Redis、mongodb、elasticsearch等中间件中,应对高并发访问场景,减轻mysql压力,防止数据库宕机。在项目开发中,为了不会原有代码进行侵入,采用c......
  • Spring Boot 中使用Caffeine缓存的简单例子
    Caffeine缓存是Java的高性能缓存库。本文简单记录下Caffeine缓存的用法。依赖配置<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency&g......
  • js 异步函数策略
    因为简单实用,所以异步函数很快成为JavaScript项目使用最广泛的特性之一。不过,在使用异步函数时,还是有些问题要注意。实现sleep()很多人在刚开始学习JavaScript时,想找到一个类似Java中Thread.sleep()之类的函数,好在程序中加入非阻塞的暂停。以前,这个需求基本上都通过set......