关于线程
默认情况下一个进程只包含一个线程。
线程可以派生其他线程,因此在任意时刻,一个进程可能包含不同状态的多个线程,它们执行程序的不同部分。
如果一个进程拥有多个线程,它们将共享进程的资源。
系统为处理器执行所调度的单元是线程,不是进程。
异步编程演示
异步获取百度的html然后异步写入test.txt文件中。
1 2 3 4 5 6 7 8 9 10
| namespace Test { internal class Program { static async Task Main(string[] args) { HttpClient client = new HttpClient(); string str = await client.GetStringAsync(@"https:\\www.baidu.com"); string fileName = @"F:\test.txt"; await File.WriteAllTextAsync(fileName,str); } } }
|
异步方法在其完成所有工作之前就返回到调用方法。
主函数调用异步方法,然后主函数在异步方法执行时继续执行(可能在相同的线程也可能在不同的线程)
async:声明这种方法是异步的。
await:在异步方法内等待。
异步方法的注意点
异步方法头中用async修饰符,即使返回结果为空也应该将结果声明为Task.
异步方法包含一个或多个await。
异步方法必须返回void、Task、Task<T>、ValueTask<T>的其中之一。
异步方法应具有公开可访问的GetAwaiter方法类型。
异步方法名称一般以Async为后缀。
Lambda表达式和匿名方法也可以作为异步对象。
await
await操作符帮我们提供异步等待,并且可以将Task<T>所封装的真实值取出:
1 2 3 4 5 6 7 8 9
| static async Task Main(string[] args) { HttpClient client = new HttpClient(); //不用await帮忙,我就要自己取 Task<string> str1 = client.GetStringAsync(@"https:\\www.baidu.com"); await Console.Out.WriteLineAsync(str1.Result); //await帮我取值 string str2 = await client.GetStringAsync(@"https:\\www.baidu.com"); await Console.Out.WriteLineAsync(str2); }
|
await表达式制订了一个异步执行的任务,这个任务可能是Task也可能不是,默认情况下这个任务在当前线程上异步运行。
一个空闲对象既是一个awaitable类型的实例,awaitable类型是指包含GetAwaiter方法的类型,该方法没有参数,返回一个awaiter类型的对象。awaiter类型包含以下成员:
bool IsCompleted{get}
void OnCompleted(Action);
还包含以下成员之一:
void GetResult();
T GetResult();
实际上,你不需要构建自己的awaitable,相反,你应该使用Task或ValueTask类,它们是awaitable类型
Task.Run
异步方法不等于多线程,异步方法的代码并不会自动在新线程中执行,除非把代码放到新线程中执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| internal class Program { static async Task Main(string[] args) { Console.WriteLine("ThreadID:{0}", Thread.CurrentThread.ManagedThreadId); await Repeat(100); Console.WriteLine("ThreadID:{0}", Thread.CurrentThread.ManagedThreadId); } static async Task<int> Repeat(int times) { int i = 0; for(int j = 0; j < times; j++) { i = i + j; } Console.WriteLine("ThreadID:{0}", Thread.CurrentThread.ManagedThreadId); return i; } }
|
Task.Run()方法可以创建一个Task,并且在不同线程上运行你的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| static async Task Main(string[] args) { Console.WriteLine("ThreadID:{0}", Thread.CurrentThread.ManagedThreadId); await Repeat(100); Console.WriteLine("ThreadID:{0}", Thread.CurrentThread.ManagedThreadId); } static async Task<int> Repeat(int times) { return await Task.Run(() => { int i = 0; for (int j = 0; j < times; j++) { i = i + j; } Console.WriteLine("ThreadID:{0}", Thread.CurrentThread.ManagedThreadId); return i; }); } }
|
它接受一个委托,委托类型可以是:

为什么有些异步方法没标async
如果不需要传递异步结果,不需要包装成Task,就没必要async,在哪await都是await。
这样写对优化有帮助。
1 2 3 4 5 6 7 8 9
| static async Task Main(string[] args) { string str = await GetBaidu(); await Console.Out.WriteLineAsync(str); } static Task<string> GetBaidu() { HttpClient client = new HttpClient(); return client.GetStringAsync("https://www.baidu.com"); } }
|
取消异步操作CancellationToken和CancellationTokenSource
位于System.Threading.Tasks名称空间下。
很多异步方法都有CancellationToken参数用于获得提前终止执行的信号。
CancellationToken是一个结构体,它包含着一个任务是否应被取消的信息。
拥有CancellationToken对象的任务应定期检查其令牌状态也就是IsCancellationRequested属性是否为true,任务需停止其操作并返回。
CancellationToken是不可逆的只能使用一次。
CancellationTokenSource对象创建可分配给不同任务的CancellationToken对象。任何持有CancellationTokenSource的对象都可以调用其Cancel方法,这会将CancellationToken的IsCancellationRequested设置为true。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| internal class Program { static async Task Main(string[] args) { CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); CancellationToken token = cancellationTokenSource.Token; cancellationTokenSource.CancelAfter(3000); Task<int> res = GetBaidu(token); while (true) { if (Console.ReadLine() != "p") { } cancellationTokenSource.Cancel(); } } static async Task<int> GetBaidu(CancellationToken token) { HttpClient client = new HttpClient(); for(int i = 0; i < 65533; i++) { if(token.IsCancellationRequested) { await Console.Out.WriteLineAsync("请求被取消"); return 1; } string str = await client.GetStringAsync("https://www.baidu.com"); await Console.Out.WriteLineAsync(str); } return 0; } }
|
同步等待任务
可以使用task对象的Wait()方法在调用方法中同步等待:
1 2 3 4 5
| static async Task Main(string[] args) { HttpClient client = new HttpClient(); Task<string> t = client.GetStringAsync("https://www.baidu.com"); t.Wait(); }
|
Task.WaitAll()静态方法用于等待一组任务都完成
Task.WaitAny()静态方法用于等待一组任务中的某一个完成
1 2 3 4 5 6 7 8 9
| static async Task Main(string[] args) { HttpClient client = new HttpClient(); Task<string> t1 = client.GetStringAsync("https://www.baidu.com"); Task t2 = File.ReadAllTextAsync(@"F:\test.txt"); //Task.WaitAll(t1, t2); Task.WaitAny(t1, t2); Console.WriteLine($"t1是否完成:{t1.IsCompleted},t2是否完成{t2.IsCompleted}"); } }
|
它们的重载还有:

在异步方法中异步等待
有时在异步方法中,你会希望用await表达式来等待Task,这时异步方法会返回调用方法,但该异步方法会等待一个或所有任务完成。可以通过Task.WhenAll()和Task.WhenAny()来实现。
1 2 3 4 5 6 7 8 9 10 11 12 13
| internal class Program { static async Task Main(string[] args) { await Test(); } static async Task Test() { HttpClient client = new HttpClient(); Task<string> t1 = client.GetStringAsync("https://www.baidu.com"); Task t2 = File.ReadAllTextAsync(@"F:\test.txt"); //await Task.WhenAll(t1, t2); await Task.WhenAny(t1, t2); await Console.Out.WriteLineAsync($"t1是否完成:{t1.IsCompleted},t2是否完成{t2.IsCompleted}"); } }
|
Task.Delay
该方法创建Task对象,将对象暂停在其线程中,一定时间后再完成,该方法不会阻塞主线程.
GUI程序中的异步
1 2 3 4 5 6 7
| private void Button_Click(object sender, RoutedEventArgs e) { this.input.Text = "Watting"; this.btn.IsEnabled = false; Thread.Sleep(4000); this.input.Text = "没有任务"; this.btn.IsEnabled = true; }
|
在wpf中当点击按钮,文本框文字没有改变,按钮也没有被禁用,但是页面卡住了三秒,期间无法拖动也无法进行其他活动。
原因是GUI需要处理很多事情,当点击按钮时,按钮的Click被放入消息队列,消息泵从队列中移除该消息并开始执行事件处理函数。事件处理函数将改变文本框文字、禁用按钮、移动窗体、改变文本框文字、启用按钮放入消息泵,消息泵还未刷新页面时,被Thread.Sleep()阻塞了主线程,当三秒之后再处理消息泵的内容,但是这一切发生的太快。
将上述代码修改如下即可:
1 2 3 4 5 6 7
| private async void Button_Click(object sender, RoutedEventArgs e) { this.input.Text = "Watting"; this.btn.IsEnabled = false; await Task.Delay(4000); this.input.Text = "没有任务"; this.btn.IsEnabled = true; }
|
Task.Yield
该方法创建一个立即返回的awaitable。等待一个Yield可以让异步方法在执行后续部分的同时返回到调用方法,可以将其理解成离开当前的消息队列、回到队伍末尾、让处理器有时间处理其他任务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| internal class Program { static void Main(string[] args) { Task<int> value = DoStuff.FindSeriesSum(100000); CountBig(100000); CountBig(100000); CountBig(100000); CountBig(100000); Console.WriteLine($"Sum:{value.Result}"); } private static void CountBig(int value) { for (int i = 0; i < value; i++) { } } } static class DoStuff { public static async Task<int>FindSeriesSum(int i1) { int sum = 0; for(int i = 0; i < i1; i++) { sum += i; if(i%1000 ==0)await Task.Yield(); } return sum; } }
|
Yield方法在GUI程序中非常有用,可以中断大量工作,让其他任务使用处理器。
异步Lambda表达式
1
| Button.Click+=async(sender,e)=>{...};
|