C#异步编程
文章目录
- 前言
- 一、异步方法1
- 二、异步方法2
- 二、异步方法3
- 二、异步方法4
- 二、异步方法5
- 二、异步方法6
- 二、异步方法7
前言
异步编程
一、异步方法1
异步方法:用async关键字修饰的方法
1、异步方法的返回值一般是Task,T是真正的返回值类型,Task。惯例:异步方法名字以Async结尾。
2、即使方法没有返回值,也最好把返回值声明为非泛型的Task.
// 返回值声明为void不建议
public async void GetFileCharLenght(string filePath)
{string s = await File.ReadAllTextAsync(filePath);
}
// 返回值声明为非泛型的Task 建议
public async Task GetFileCharLenght(string filePath)
{string s = await File.ReadAllTextAsync(filePath);
}
3、调用泛型方法时,一般在方法前加上await关键字,这样拿到的返回值就是泛型指定的T类型
// 拿到的返回值就是泛型指定的string类型
public async Task<string> GetFileCharLenght(string filePath)
{string s = await File.ReadAllTextAsync(filePath);//方法前加上await关键字return s;
}
4、异步方法的传染性:一个方法种如果有await调用,则这个方法就必须修饰为async
// 一个方法种如果有await调用,则这个方法就必须修饰为async
public async Task Main()
{await GetFileCharLenght(@"C:\a.txt");
}public async Task<string> GetFileCharLenght(string filePath)
{string s = await File.ReadAllTextAsync(filePath);return s;
}
二、异步方法2
如果同样的功能,既有同步方法,又有异步方法,那么首先使用异步方法。,对于不支持的异步方法,可以使用Wait()(无返回值);Result()(有返回值);风险锁死,尽量不要使用。
// 假如MainWindow不支持异步操作,也就是不能用async关键字修改,
// 而File.ReadAllTextAsync(@"C:\a.txt")这个异步方法有返回值,
// 可以使用Result关键字,将异步方法的返回值取出
public MainWindow()
{InitializeComponent();string s = File.ReadAllTextAsync(@"C:\a.txt").Result;
}
// 假如Main Window不支持异步操作,而File.ReadAllTextAsync(@"C:\a.txt")这个异步方法有返回值,可以使用Result关键字
public MainWindow()
{InitializeComponent();string s = File.ReadAllTextAsync(@"C:\a.txt").Result;// 如果没有返回值可以使用Wait等待异步方法执行完毕File.WriteAllTextAsync(@"C:\a.txt", "11111111").Wait();
}
lamda表达式中使用异步方法
// 异步lamda表达式,在lamda表达式中使用async 关键字修饰即可
ThreadPool.QueueUserWorkItem(async (obj) =>
{await File.WriteAllTextAsync(@"C:\a.txt", "11111111");});
二、异步方法3
异步方法不等于多线程,异步方法的代码不会自动在新线程中执行,除非把代码放到新线程中执行。
// 执行前后线程ID不变
static async Task Main()
{// To customize application configuration such as set high DPI settings or default font,// see https://aka.ms/applicationconfiguration.ApplicationConfiguration.Initialize();Application.Run(new Form1());Debug.WriteLine("之前"+Thread.CurrentThread.ManagedThreadId);double r = await CalcAsync(5000);Debug.WriteLine($"r={r}");Debug.WriteLine("之后" + Thread.CurrentThread.ManagedThreadId);
}
public static async Task<double> CalcAsync(int n)
{Debug.WriteLine("CalcAsync"+Thread.CurrentThread.ManagedThreadId);double result = 0;Random rnd = new Random();for (int i = 0; i < n; i++) {result += rnd.NextDouble();}return result;
}
// 结果
/*
之前1
CalcAsync1
r=2495.1055014091776
之后1
*/
除非把代码放到新线程中执行。必须手动将代码放入线程中,也就是使用Task.Run()方法,会自动根据返回值推断出Task.Run()泛型的类型
public static async Task<double> CalcAsync(int n)
{return await Task.Run(() =>{Debug.WriteLine("CalcAsync" + Thread.CurrentThread.ManagedThreadId);double result = 0;Random rnd = new Random();for (int i = 0; i < n; i++){result += rnd.NextDouble();}return result;});
}
// 运行结果
/*
之前1
CalcAsync 5
r=2498.2461521182872
之后5
前后线程id不一样,异步方法执行完后被放到新的线程中执行了
*/
二、异步方法4
如果想要在异步方法中暂停一段时间,不要用Thread.Sleep(),因为它会阻塞调用线程,而要用await Task.Delay()。
例如,在winfrom程序中,如果使用Thread.Sleep()就会阻塞UI线程在睡眠这段时间是无法操作窗体.
private async void button1_Click(object sender, EventArgs e)
{using (HttpClient client = new HttpClient()) {string s1 = await client.GetStringAsync("https://www.youzack.com");textBox1.Text = s1.Substring(0,20);Thread.Sleep(5000);string s2 = await client.GetStringAsync("https://www.baidu.com");textBox1.Text = s2.Substring(0,20);}
}
效果
使用 await Task.Delay()
private async void button1_Click(object sender, EventArgs e)
{using (HttpClient client = new HttpClient()) {string s1 = await client.GetStringAsync("https://www.youzack.com");textBox1.Text = s1.Substring(0,20);await Task.Delay(5000);string s2 = await client.GetStringAsync("https://www.baidu.com");textBox1.Text = s2.Substring(0,20);}
}
效果
二、异步方法5
CancellationToken
有时候需要提前终止任务,比如,请求超时、用户取消请求。很多异步方法都有CancellationToken参数,用于获得提前终止执行的信号。
CancellationToken是一个结构体
有个 None:空的成员
bool IsCancellationRequested 是否发出取消任务的请求
ThrowIfCancellationRequested() 如果任务被取消,执行到这句话就抛异常。
一般不直接使用CancellationToken来获取CancellationToken对象,而是使用CancellationTokenSource来获取CancellationToken对象。CancellationTokenSource中的方法,CancelAfter() 超时后发出取消信号,Cancel()发出取消信号,
private async void button1_Click(object sender, EventArgs e)
{Download1Async("https://www.baidu.com", 100);
}
/// <summary>
/// 未使用CancellationToken,程序会一直执行直到访问改网站100次
/// </summary>
/// <param name="url"></param>
/// <param name="n"></param>
/// <returns></returns>
public async Task Download1Async(string url,int n)
{using (HttpClient client = new HttpClient()){for(var i=0; i < n; i++){string s1 = await client.GetStringAsync(url);textBox1.Text = textBox1.Text + s1.Substring(0, 20);}}
}
使用CancelAfter来终止请求
private async void button1_Click(object sender, EventArgs e)
{CancellationTokenSource source = new CancellationTokenSource();source.CancelAfter(5000);// 5秒之后终止如果请求还未结束,提前终止请求CancellationToken token = source.Token;Download2Async("https://www.baidu.com", 100 , token);
}
public async Task Download2Async(string url, int n,CancellationToken cancellationToken)
{using (HttpClient client = new HttpClient()){for (var i = 0; i < n; i++){string s1 = await client.GetStringAsync(url);textBox1.Text = textBox1.Text + s1.Substring(0, 20);if (cancellationToken.IsCancellationRequested) {textBox1.Clear();textBox1.Text = "取消请求";break;}}}
}
效果 5秒内请求未执行完,请求被提前终止。
使用ThrowIfCancellationRequested()来终止请求
private async void button1_Click(object sender, EventArgs e)
{CancellationTokenSource source = new CancellationTokenSource();source.CancelAfter(5000);// 5秒之后终止如果请求还未结束,提前终止请求CancellationToken token = source.Token;Download2Async("https://www.baidu.com", 100 , token);
}
public async Task Download2Async(string url, int n,CancellationToken cancellationToken)
{using (HttpClient client = new HttpClient()){for (var i = 0; i < n; i++){string s1 = await client.GetStringAsync(url);textBox1.Text = textBox1.Text + s1.Substring(0, 20);//if (cancellationToken.IsCancellationRequested) //{// textBox1.Clear();// textBox1.Text = "取消请求";// break;//}cancellationToken.ThrowIfCancellationRequested();}}
}
抛出异常引发的异常:“System.OperationCanceledException”(位于 System.Private.CoreLib.dll 中)
使用GetStringAsync,时传入cancellationToken,让GetStringAsync来处理。
private async void button1_Click(object sender, EventArgs e)
{CancellationTokenSource source = new CancellationTokenSource();source.CancelAfter(5000);// 5秒之后终止如果请求还未结束,提前终止请求CancellationToken token = source.Token;Download3Async("https://www.baidu.com", 100 , token);
}
public async Task Download3Async(string url, int n, CancellationToken cancellationToken)
{using (HttpClient client = new HttpClient()){for (var i = 0; i < n; i++){var req = await client.GetStringAsync(url,cancellationToken);textBox1.Text = textBox1.Text + (await req.Content.ReadAsStringAsync()).Substring(0, 20);}}
}
引发的异常:“System.Threading.Tasks.TaskCanceledException”(位于 System.Private.CoreLib.dll 中)
总结:
在ASP.Net Core开发中,一般不需要自己处理CancellationToken、CancellationTokenSource这些,只要做到,能转发CancellationToken就转发即可
二、异步方法6
WhenAll
Task类的重要方法:
1、Task<Task>
WhenAny(IEnumerable<Task>
tasks)等,任何一个Task完成,Task就完成
2、Task<TResult[]> WhenAll<TResult>
(params <TResult[]> tasks)等,所有Task完成才完成。用于等待多个任务执行结束,但是不在乎它们的执行顺序。
3、FromResult()创建普通数值的Task对象。
Task<TResult[]> WhenAll<TResult>
(params <TResult[]> tasks),当3个文档都被读完成才算完成。
public async Task GetAllFileChar()
{Task<string> t1 = File.ReadAllTextAsync(@"C:\a.txt");Task<string> t2 = File.ReadAllTextAsync(@"C:\b.txt");Task<string> t3 = File.ReadAllTextAsync(@"C:\c.txt");string[] strs = await Task.WhenAll(t1, t2, t3);string s1 = strs[0];string s2 = strs[1];string s3 = strs[2];Debug.WriteLine(s1);Debug.WriteLine(s2);Debug.WriteLine(s3);}
二、异步方法7
异步与yield
yield return 不仅能够简化数据的返回,而且可以让数据处理流水线化,提示性能
C#的迭代器块(yield return) 实现惰性序列生成。C#的迭代器块(yield return) 实现惰性序列生成。
static IEnumerable<string> Test()
{yield return "hello";yield return "hello1";yield return "hello2";
}
在旧版C#中,async方法中不能使用yield,从c#8.0开始,把返回值声明为IAsyncEnumerable(不要带Task),然后遍历的时候用await foreach()即可。
private async void button1_Click(object sender, EventArgs e)
{await foreach(var s in Test1()){textBox1.Text = s;}
}
static async IAsyncEnumerable<string> Test1()
{yield return "hello";yield return "hello1";yield return "hello2";
}