本节内容大概分为 async背后的线程切换、异步方法不等于多线程、为什么有的异步方法没有标async、sleep()方法
await调用的等待期间,.NET框架会把当前的线程返回给线程池,等异步方法调用执行完毕后,框架会从线程池再取出一个线程执行后续代码
这里有一个例子来帮助大家理解
先用Thread .CurrentThread.ManagedThreadId来获取当前的线程ID,然后我们进行一个将字符串写入文件的异步方法,再用Thread .CurrentThread.ManagedThreadId来查看当前进程的ID
using System.Text;
//Thread .CurrentThread.ManagedThreadId获取当前线程的ID
Console.WriteLine(Thread .CurrentThread.ManagedThreadId);
StringBuilder sb = new StringBuilder();
//如果执行次数过少,线程可能就是一个,取决电脑性能
for (int i =0; i < 1000; i++)
{
sb.Append("XXXXXXXXXXXXXXXX");
}
await File.WriteAllTextAsync(@"D:\.NET core\验证线程.txt",sb.ToString());
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
从这里我们可以看出前后完成任务的不是同一个线程
当然如果执行次数过少,所使用的线程可能就是一个,取决电脑性能
这是一种系统的优化:到要等待的时候,如果发现当前任务以及执行结束,那也就没必要切换线程了,剩下的代码就在之前的线程上继续执行了。(框架自己对其的优化,并不是程序员对其的优化)
就像上面的那个例子把字符串改写为简单的数值,获取到的线程ID就都是1
异步方法的代码并不会自动在新线程中执行,除非把代码放到新线程中执行。
这是一个验证的例子
namespace 异步方法不等于多线程
{
internal class Program
{
static async Task Main(string[] args)
{
//这里线程调用堆栈可以看到使用的线程是1,但是没有正常打印出来,使用的是Thread.CurrentThread.ManagedThreadId
Console.WriteLine(Environment.CurrentManagedThreadId);//使用这个方法后成功打印出来了
Console.WriteLine("之前:", +Environment.CurrentManagedThreadId);
double r = await CalcAsync(50);
Console.WriteLine($"r={r}");
Console.WriteLine("之前:", +Environment.CurrentManagedThreadId);
}
public static async Task CalcAsync(int n)
{
/* Console.WriteLine("CalcAsync", +Thread.CurrentThread.ManagedThreadId);
double result = 0;
Random random = new Random();
for (var i = 0; i < n * n; i++)
{
result += random.NextDouble();
}
return result;*/
//run方法,把方法放到一个新的线程里面去
return await Task.Run(() =>
{
Console.WriteLine("CalcAsync", +Environment.CurrentManagedThreadId);
double result = 0;
Random random = new Random();
for (var i = 0; i < n * n; i++)
{
result += random.NextDouble();
}
return result;
});
}
}
}
首先我们要了解关于async的缺点
因此有的方法不写async,直接使用Task,不“拆完了再装”反编译上面的代码,只是普通的方法调用
优点:运行效率高,不会造成线程浪费
(返回值为Task的不一定都要标注async,标准async只是让我们可以更方便的使用await)
什么时候用async?什么时候可以不写async?
如果一个异步方法只是对别的异步方法调用的转发,并没有太多复杂的逻辑(比如等待A的结构,再调用D;把A调用的返回值拿到内部做一些处理在返回),那么就可以去掉async关键字,当然如果调用异步方法的时候对其做了其他的操作,就要加上async
这里是一个简单的对比方法,二者都是读取一个文件
namespace 没有async关键字的异步方法
{
internal class Program
{
static async Task Main(string[] args)
{
string s = await ReadAsync(1);
Console.WriteLine(s);
}
//传统的异步方法
/* static async Task ReadAsync(int num)
{
if (num == 1)
{
string s = await File.ReadAllTextAsync(@"D:\.NET core\没有async关键字的异步方法\1.txt");
return s;
}
else if(num==2)
{
string s = await File.ReadAllTextAsync(@"D:\.NET core\没有async关键字的异步方法\2.txt");
return s;
}
else
{
throw new ArgumentException();
}
}*/
//普通方法调用,运行效率更高
static Task ReadAsync(int num)
{
if (num == 1)
{
return File.ReadAllTextAsync(@"D:\.NET core\没有async关键字的异步方法\1.txt");
}
else if (num == 2)
{
return File.ReadAllTextAsync(@"D:\.NET core\没有async关键字的异步方法\2.txt");
}
else
{
throw new ArgumentException();
}
}
}
}
尽量少去使用sleep()方法
如果想在异步方法中暂停一段时间,不要用Thread.sleep();使用该方法是阻塞调用线程,在这里可能会阻塞主进程,我们可以使用await.Task.Delay()(delay阻塞的是毫秒delay(3000)也就是3s);该方法不会阻塞主线程
这是一个简单的winform实验,用到了一个button控件和一个textbox控件
button事件代码:
namespace 异步休息_sleep和await区别_
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private async void button1_Click(object sender, EventArgs e)
{
using (HttpClient httpClient = new HttpClient())
{
string s1 = await httpClient.GetStringAsync("https://www.baidu.com");
textBox1.Text = s1.Substring(0, 20);
// Thread.Sleep(3000);
//使用sleep方法后进程容易卡死在这里
await Task.Delay(3000);
string s2 = await httpClient.GetStringAsync("https://www.youzack.com");
textBox1.Text = s2.Substring(0, 100);
}
}
}
}