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

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

时间:2024-01-27 20:05:30浏览次数:24  
标签:异步 同步 string int Request url while 例子 true

请看一个示例:

同步方式请求接口

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

代码

一个for循环,循环500次,调用方法Request,Request方法中一个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++)
    {
        Request(i);
    }

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

static void Request(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);
}

运行截图

一个例子形象地理解同步与异步_for循环

说明

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

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

代码

static void Request(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);
    }
}

运行截图

一个例子形象地理解同步与异步_for循环_02

说明

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

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

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

代码

static async void Request(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);
    }
}

运行截图

一个例子形象地理解同步与异步_for循环_03

说明

速度快多了,并且越来越快。
有多个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 Request(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);
    }
}

运行截图

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

说明

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

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

代码

static async void Request(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);
    }
}

运行截图

一个例子形象地理解同步与异步_for循环_05

说明

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

总结

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

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

运行截图:

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


说明:

20个while(true)都在运行,比一个while(true)要快很多。

当然,没必要这么写了,直接new 20个Thread就可以。

但如果for循环就是500次,而且需要调用的IO接口又是同步的,那么就老老实实写500个new Thread。

如果非要用异步,设置一下线程池的大小,大于500,避免线程饥饿。

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

你会发现,不能想当然,依然有问题,这时强行用异步就很容易写出BUG了。
最后,再好好体会一下上面的例子。

补充

对于修改2的修改:没必要启动500个while(true),可以只启动一个,代码如下:

static void Main(string[] args)
{
    // 设置一下线程池数量可以大大提升批量请求速度
    // ThreadPool.SetMinThreads(650, 650);
    // ThreadPool.SetMinThreads(600, 600); 

    CalcSpeed();

    Task.Factory.StartNew(() =>
    {
        while (true)
        {
            // 批量请求,第1个参数是一个集合,第2个参数是最大并行度
            Parallel.ForEach(Enumerable.Range(0, 500), new ParallelOptions() { MaxDegreeOfParallelism = 500 }, (i, c) =>
            {
                Request(i);
            });

            Thread.Sleep(1);
        }
    }, TaskCreationOptions.LongRunning);

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

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

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



标签:异步,同步,string,int,Request,url,while,例子,true
From: https://blog.51cto.com/u_5496753/9444215

相关文章

  • [postgres]配置主从异步流复制
    前言环境信息IP角色操作系统PostgreSQL版本192.168.1.112主库Debian1215.3192.168.1.113从库Debian1215.3配置主从修改主库的postgresql.conf文件。修改此配置文件需重启数据库服务。归档脚本内容见"附录-clean_archivelog"listen_addresses='*......
  • logstash实现Mysql(Tidb)数据到Elasticsearch数据迁移(增量同步)
    1、下载Logstash和Elasticsearch,需要版本一致,本次使用版本均为7.8.0版本[root@zjkdata]#llelasticsearch-7.8.0-linux-x86_64.tar.gzlogstash-7.8.0.tar.gz-rw-r--r--.1rootroot31911256112月2815:39elasticsearch-7.8.0-linux-x86_64.tar.gz-rw-r--r--.1root......
  • 批量数据之DataX数据同步
    目录1DataX1.1引言1.2DataX简介1.3核心1.3.1DataX3.0框架设计1.3.2DataX3.0核心架构1.4使用DataX实现数据同步1.4.1准备安装1.4.2Linux上安装DataX软件1.4.3DataX基本使用1.4.4MySQL数据库1.4.4.1安装1.4.4.2准备同步1.4.4.3创建存储过程:1.4.5通过DataX......
  • 爬虫-异步抓取一部小说
    一、利用requests请求同步和aiohttp异步,两个结合来获取小说里的内容1、先利用cookie和session来实现登录根据post请求,带入参数来建立会话,并获取session利用session来进行同步请求获取,每一章节的名称和链接地址 通过上面的图,发现在/html/body/div[5]/dl/dd范围内的a标签......
  • 使用debezium-connector-jdbc组件完成数据同步(io.debezium.connector.jdbc.JdbcSinkCo
    1.情景展示在网络上几乎找不到关于debezium-connector-jdbc插件的博客文章,基本上都在吹io.confluent.connect.jdbc.JdbcSinkConnector,由于一开始对数据同步插件并不了解,导致自己走了不少弯路。生产数据组件:debezium-connector-mysql、debezium-connector-oracle等数据库组件,通......
  • Python中为何使用新语法而不是装饰器来实现async/await异步功能
    Python是一种多范式编程语言,通过引入新的语法和特性,不断提升其功能和灵活性。在异步编程领域,Python引入了async/await关键字来实现协程和异步操作,而不是使用已有的装饰器语法。本文将探讨为何Python选择引入新语法来实现async/await异步功能,以及与装饰器的区别和优势。一、理解异步......
  • RuoYI 框架 异步任务管理
    核心目标代码AsyncManager.me().execute(AsyncFactory.recordLogininfor(username,Constants.LOGIN_FAIL,e.getMessage())) 代码解读  1、异步任务管理类AsyncManager获取Bean对象  2、线程池配置类ThreadPoolConfig创建线程池Bean对象  3、异步工厂AsyncFactory......
  • 事件循环-同步异步-计时器精确问题
    消息队列的解释每个任务都有一个任务类型。同一个类型的任务必须在一个队列中。不同类型的任务可以分属于不同的队列中。在一次事件循环中,浏览器可以【根据实际情况】从不同的队列中取出任务执行。浏览器必须准备好一个微队列,微队列中的任务优先其他所有类型的任务。chrome......
  • 如何实现高精度无线同步控制矿山爆破?
        随着现代社会工业化进程的不断加快,人们对于矿山开采的规模和速度要求越来越高。为了快速推进矿山的开采作业,人们对于炸yao威力的要求越来越高。    考虑到生产、运输和存储等各个方面的安全性以及国家有关方面的强制要求,雷管的火药填装量是有严格限制的,不允许......
  • .net 高并发(一,异步编程模型)
    在.NET中,异步编程模型(Async/Await)是一种处理高并发的好方法。它允许开发人员以非阻塞的方式编写异步代码,从而使应用程序能够同时处理多个请求或任务,从而提高并发性能。下面是使用Async/Await进行异步编程的一般步骤:定义一个返回Task或Task<TResult>的方法,并在方法签名中使用as......