请看一个示例:
请求一次接口耗时大约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 dict = new ConcurrentDictionary();
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);
}
代码中没有创建线程,也没有使用Task.Run,请求一次接口耗时大约100多毫秒,while(true)在主线程中执行,平均1秒请求接口不到10次。注意:只有第一个while(true)在执行。
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);
}
}
没什么用,速度还变慢了一点。依然是有问题的代码。依然只有第一个while(true)在执行。
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);
}
}
速度快多了,并且越来越快。有多个while(true)在执行,并且在执行的while(true)数量越来越多,最终会达到500个。这是比较神奇的地方,仅仅加了一行await Task.Delay(1);同步方法Request就变成了异步方法。在执行await Task.Delay(1);这一行时,其它while(true)得到了执行机会,你们可以验证一下。同步请求分别在不同的线程中执行,你们可以打印线程ID验证一下。
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);
}
}
速度非常快。异步的优势体现出来了。
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);
}
}
线程饥饿,全部阻塞,没有返回结果,速度是0。
通过这个例子形象地体会一下同步与异步,以及为什么要使用异步。如果你写的代码是异步的,但是调用的IO接口又是同步的,这比真正的异步效率要差很多,但比同步代码有所提升。针对修改2,有人会说,这代码有问题,后面的while(true)会延迟好久才会执行。但是如果for循环的数量是少量的,程序启动时的一点延迟是允许的,就没有问题,修改代码如下:
for (int i = 0; i < 20; i++)
{
Request(i);
}
运行截图:
说明:20个while(true)都在运行,比一个while(true)要快很多。当然,没必要这么写了,直接new 20个Thread就可以。但如果for循环就是500次,而且需要调用的IO接口又是同步的,那么就老老实实写500个new Thread。如果非要用异步,设置一下线程池的大小,大于500,避免线程饥饿。
ThreadPool.SetMinThreads(800, 800);
ThreadPool.SetMinThreads(600, 600);
你会发现,不能想当然,依然有问题,这时强行用异步就很容易写出BUG了。最后,再好好体会一下上面的例子。
文章转载自:0611163
原文链接:https://www.cnblogs.com/s0611163/p/17979998
体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构