## 使用C# 6.0中的async/await
异步函数
是TPL之上更高级别的抽象,真正简化了异步编程,它与普通函数不一样在于必须有async
标识,并且返回类型一般是Task<T>,Task
类型,当然也可以使用async void
,但更推荐使用async Task
,使用async void
唯一合理的地方在于程序中使用顶层UI控制器事件处理器的时候。
在异步函数
内部,必须至少有一个await
操作符,该操作符一般与TPL一起工作,并获取该任务中的异步操作的结果。需要注意的是在执行完await
调用的代码行后该方法会立即返回,剩下的异步函数的部分独立异步运行,值得注意的是如果程序中有两个连续的await操作符,它们是顺序运行的,即第一个完成后,第二个才会开始运行。
结合线程的角度来理解,即,执行完await操作后,.NET会把当前的线程返回线程池,等异步方法调用执行完毕后((异步方法的代码并不会在新线程中执行,除非把代码放到新线程中执行,比如用Task.Run方法),框架再从线程池再取出来一个线程执行后续的代码,把当前线程返回线程池就意味着具备并发的能力,就像餐厅模型中的服务员离开去欢迎其他客人一样,当异步方法调用执行完毕后,再取出一个线程,就像又一个服务员过来继续在await后的代码处进行服务,运行,所以这次过来的服务员有可能是一开始的服务员,也有可能是别的服务员,所以此时的线程有可能是之前的线程,也有可能是其他线程。
还需要甄别是否是异步方法,
比如:
static async Task<int> GetPrimesCountAsync(int start,int count)
{
Console.WriteLine("GetPrimesCountAsync所在线程的ID:" + Thread.CurrentThread.ManagedThreadId);
return Task.Run(()=>
{
Console.WriteLine("Run中所在的线程ID:"+Thread.CurrentThread.ManagedThreadId);
return ParallelEnumerable.Range(start, count).Count(n =>
Enumerable.Range(2, (int)Math.Sqrt(n) - 1).All(i => n % i > 0));
}
);
}
其实并不是异步方法,因为await,asyn没有成对,所以造成工作线程可以运行到 Console.WriteLine("GetPrimesCountAsync所在线程的ID:" + Thread.CurrentThread.ManagedThreadId);
直至遇到Task.Run,手动开启一个新的任务。
await
关键字简化了连续性的附着(Simplify the attaching of continuations)。
比如:
var result=await expression;
statement(s);
等价于:
var awaiter=expression.GetAwaiter();
awaiter.OnCompleted(()=>{
var result=awaiter.GetResult();
statement(s);
});
上面的意思即为,主程序运行到await处,立即返回,返回给线程池,await中的表达式同时异步进行,并且约定,在表达式结束后,再调用statement(s),statement(s)就是运行在再从线程池中取出的新的线程上。
可以用以下demo佐证:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ThreadDemo
{
class Program21
{
static async Task<string> GetInfoAsync(string name,int seconds)
{
//await Task.Delay(TimeSpan.FromSeconds(seconds));
await Task.Run(() => {
Thread.Sleep(TimeSpan.FromSeconds(seconds));
Console.WriteLine($"{name} first :{Thread.CurrentThread.ManagedThreadId}");
});
await Task.Run(() => {
Thread.Sleep(TimeSpan.FromSeconds(seconds));
Console.WriteLine($"{name} second:{Thread.CurrentThread.ManagedThreadId}");
});
return $"Task {name} is running on a thread id " +
$"{Thread.CurrentThread.ManagedThreadId}. " +
$"Is thread pool thread:{Thread.CurrentThread.IsThreadPoolThread}";
}
static async Task AsyncProcess()
{
Task<string> t1 = GetInfoAsync("Task 1", 3);
Task<string> t2 = GetInfoAsync("Task 2", 5);
string[] results = await Task.WhenAll(t1, t2);
foreach(string result in results)
{
Console.WriteLine(result);
}
}
static void Main()
{
Task t = AsyncProcess();
t.Wait();
}
}
}
output:
Task 1 first :3
Task 2 first :4
Task 1 second:5
Task 2 second:3
Task Task 1 is running on a thread id 5. Is thread pool thread:True
Task Task 2 is running on a thread id 3. Is thread pool thread:True
这也说明了为什么上述GetInfoAsync
返回的是Task<string>
类型,因为await关键字后的任务后面的statement,实际上就是在最后一个await的任务的线程里完成的,所以return $"Task {name} is running on a thread id " + $"{Thread.CurrentThread.ManagedThreadId}. " + $"Is thread pool thread:{Thread.CurrentThread.IsThreadPoolThread}";
自然是异步中返回string的函数,即是Task<string>
类型。
await
最大的优势在于可以出现在异步函数中的任意地方(除了lock,unsafe环境)。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace ThreadDemo
{
class Program22
{
static async Task<string> GetInoAsync(string name,int seconds)
{
Console.WriteLine($"GetInfoAsync Pre:{Thread.CurrentThread.ManagedThreadId}");
await Task.Delay(TimeSpan.FromSeconds(seconds));
Console.WriteLine($"GetInfoAsync After:{Thread.CurrentThread.ManagedThreadId}");
//throw new Exception($"Boom from {name}!");
return $"In {Thread.CurrentThread.ManagedThreadId}";
}
static async Task AsyncProcess()
{
Console.WriteLine("1. Single exception");
try
{
Console.WriteLine($"AsyncProcess Pre:{Thread.CurrentThread.ManagedThreadId}");
string result = await GetInoAsync("Task 1", 2);
Console.WriteLine(result);
Console.WriteLine($"AsyncProcess after:{Thread.CurrentThread.ManagedThreadId}");
}
catch(Exception ex)
{
Console.WriteLine($"Exception details:{ex}");
}
}
static void Main()
{
Console.WriteLine($"Main ID:{Thread.CurrentThread.ManagedThreadId}");
Task t = AsyncProcess();
t.Wait();
}
}
}
output:
Main ID:1
1. Single exception
AsyncProcess Pre:1
GetInfoAsync Pre:1
GetInfoAsync After:4
In 4
AsyncProcess after:4
在main()中,添加一个输出:
static void Main()
{
Console.WriteLine($"Main ID:{Thread.CurrentThread.ManagedThreadId}");
Task t = AsyncProcess();
Console.WriteLine($"Before t.Wait(),Main ID is {Thread.CurrentThread.ManagedThreadId}");
t.Wait();
}
得到:
Main ID:1
1. Single exception
AsyncProcess Pre:1
GetInfoAsync Pre:1
Before t.Wait(),Main ID is 1
GetInfoAsync After:4
In 4
AsyncProcess after:4
上面的demo,清楚的显示了线程随着await的变化。
使用await操作符获取异步任务结果
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace ThreadDemo
{
class Program17
{
static async Task<string> GetInfoAsync(string name)
{
await Task.Delay(TimeSpan.FromSeconds(2));
return $"Task {name} is running on a thread id {Thread.CurrentThread.ManagedThreadId}" +
$" Is thread pool thread: {Thread.CurrentThread.IsThreadPoolThread}";
}
static Task AsynchronyWithTPL()
{
Task<string> t = GetInfoAsync("Task 1");
Task t2 = t.ContinueWith(task => Console.WriteLine(t.Result), TaskContinuationOptions.NotOnFaulted);
Task t3 = t.ContinueWith(task => Console.WriteLine(t.Exception.InnerException), TaskContinuationOptions.OnlyOnFaulted);
return Task.WhenAny(t2, t3);
}
static async Task AsynchronyWithAwait()
{
try
{
string result = await GetInfoAsync("Task 2");
Console.WriteLine(result);
}
catch(Exception ex)
{
Console.WriteLine(ex);
}
}
static void Main()
{
Task t = AsynchronyWithTPL();
t.Wait();
t = AsynchronyWithAwait();
t.Wait();
}
}
}
outputt:
Task Task 1 is running on a thread id 4 Is thread pool thread: True
Task Task 2 is running on a thread id 6 Is thread pool thread: True
如果将Task<String> GetInfoAsync
函数改为:
static async Task<string> GetInfoAsync(string name)
{
await Task.Delay(TimeSpan.FromSeconds(2));
//return $"Task {name} is running on a thread id {Thread.CurrentThread.ManagedThreadId}" +
// $" Is thread pool thread: {Thread.CurrentThread.IsThreadPoolThread}";
throw new Exception("Boom!");
}
output:
System.Exception: Boom!
在 ThreadDemo.Program17.<GetInfoAsync>d__0.MoveNext() 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program17.cs:行号 16
System.Exception: Boom!
在 ThreadDemo.Program17.<GetInfoAsync>d__0.MoveNext() 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program17.cs:行号 16
--- 引发异常的上一位置中堆栈跟踪的末尾 ---
在 System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
在 System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
在 ThreadDemo.Program17.<AsynchronyWithAwait>d__2.MoveNext() 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program17.cs:行号 30
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace ThreadDemo
{
class Program17
{
static async Task<string> GetInfoAsync(string name)
{
await Task.Run(() => { Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId} is running.."); });
await Task.Delay(TimeSpan.FromSeconds(2));
return $"Task {name} is running on a thread id {Thread.CurrentThread.ManagedThreadId}" +
$" Is thread pool thread: {Thread.CurrentThread.IsThreadPoolThread}";
//throw new Exception("Boom!");
}
static Task AsynchronyWithTPL()
{
Task<string> t = GetInfoAsync("Task 1");
Task t2 = t.ContinueWith(task => Console.WriteLine(t.Result), TaskContinuationOptions.NotOnFaulted);
Task t3 = t.ContinueWith(task => Console.WriteLine(t.Exception.InnerException), TaskContinuationOptions.OnlyOnFaulted);
return Task.WhenAny(t2, t3);
}
static async Task AsynchronyWithAwait()
{
try
{
string result = await GetInfoAsync("Task 2");
Console.WriteLine(result);
}
catch(Exception ex)
{
Console.WriteLine(ex);
}
}
static void Main()
{
Task t = AsynchronyWithTPL();
t.Wait();
t = AsynchronyWithAwait();
t.Wait();
}
}
}
output:
3 is running..
Task Task 1 is running on a thread id 4 Is thread pool thread: True
3 is running..
Task Task 2 is running on a thread id 6 Is thread pool thread: True
可以发现同一个异步函数内部,不同的await后的任务处理确实是在不同的线程池。
在lambda表达式中使用await操作符
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace ThreadDemo
{
class program18
{
static async Task AsynchronousProcessing()
{
Func<string, Task<string>> asyncLambda = async name =>
{
await Task.Delay(TimeSpan.FromSeconds(2));
return $"Task {name} is running on a thread id {Thread.CurrentThread.ManagedThreadId}." +
$" Is thread pool thread:{Thread.CurrentThread.IsThreadPoolThread}";
};
string result = await asyncLambda("async lambda");
Console.WriteLine(result);
}
static void Main()
{
Task t = AsynchronousProcessing();
t.Wait();
}
}
}
output:
Task async lambda is running on a thread id 4. Is thread pool thread:True
对连续的异步任务使用await操作符
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace ThreadDemo
{
class program19
{
static async Task<string> GetInfoAsync(string name)
{
Console.WriteLine($"Task {name} started!");
await Task.Delay(TimeSpan.FromSeconds(2));
if(name=="TPL 2")
{
throw new Exception("Boom!");
}
return $"Task {name} is running on a thread id {Thread.CurrentThread.ManagedThreadId}." +
$" Is thread pool thread:{Thread.CurrentThread.IsThreadPoolThread}";
}
static async Task AsyncronyWithAwait()
{
try
{
string result = await GetInfoAsync("Async 1");
Console.WriteLine(result);
result = await GetInfoAsync("Async 2");
Console.WriteLine(result);
}
catch(Exception ex)
{
Console.WriteLine(ex);
}
}
static void Main()
{
Task t = AsyncronyWithAwait();
t.Wait();
}
}
}
output:
Task Async 1 started!
Task Async 1 is running on a thread id 4. Is thread pool thread:True
Task Async 2 started!
Task Async 2 is running on a thread id 4. Is thread pool thread:True
对并行执行的异步任务使用await操作符
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ThreadDemo
{
class Program21
{
static async Task<string> GetInfoAsync(string name,int seconds)
{
await Task.Delay(TimeSpan.FromSeconds(seconds));
//await Task.Run(() => Thread.Sleep(TimeSpan.FromSeconds(seconds)));
return $"Task {name} is running on a thread id " +
$"{Thread.CurrentThread.ManagedThreadId}. " +
$"Is thread pool thread:{Thread.CurrentThread.IsThreadPoolThread}";
}
static async Task AsyncProcess()
{
Task<string> t1 = GetInfoAsync("Task 1", 3);
Task<string> t2 = GetInfoAsync("Task 2", 5);
string[] results = await Task.WhenAll(t1, t2);
foreach(string result in results)
{
Console.WriteLine(result);
}
}
static void Main()
{
Task t = AsyncProcess();
t.Wait();
}
}
}
output:
Task Task 1 is running on a thread id 4. Is thread pool thread:True
Task Task 2 is running on a thread id 4. Is thread pool thread:True
我们使用了Task.WhenAll辅助方法创建了另一个任务,该任务的返回类型是Task<string[]>,该任务只有在所有底层任务完成后才会运行。5s后,我们可以观察到,一瞬间全部出来了结果,这说明了这个任务在t1,t2都结束后,才执行。
但这里也观察到一个非常有趣的现象:并行任务是被线程池同一个工作者线程执行的,How can it be?
为了搞清楚这个问题,我们需要知道Task.Delay
的工作机理是怎么样的!
它是使用类似这样的技术实现的:
Task Delay(int milliseconds)
{
var tcs=new TaskCompletionSource<object>();
var timer=new System.Timers.Timer(milliseconds){AutoReset=false};//保证只调用一次
timer.Elapsed+=delegate{timer.Dispose();tcs.SetResult(null);};
timer.Start();
return tcs.Task;
}
由此可见,对于GetInfoAsync
异步方法,当timer所在的线程A达到milliseconds后,坍缩,由于await关键字,线程A将继续执行return
语句,然后A结束,重新回到线程池,当t2再运行时,那么就很有可能重新从线程池中拾取到线程A,结果就造成了t1,t2都用了同一个worker thread。
如果使用await Task.Run(() => { Thread.Sleep(TimeSpan.FromSeconds(seconds));
就肯定不会出现以上重复使用同一个worker thread的情况。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ThreadDemo
{
class Program21
{
static async Task<string> GetInfoAsync(string name,int seconds)
{
//await Task.Delay(TimeSpan.FromSeconds(seconds));
await Task.Run(() => {
Thread.Sleep(TimeSpan.FromSeconds(seconds));
Console.WriteLine($"{name} first :{Thread.CurrentThread.ManagedThreadId}");
});
await Task.Run(() => {
Thread.Sleep(TimeSpan.FromSeconds(seconds));
Console.WriteLine($"{name} second:{Thread.CurrentThread.ManagedThreadId}");
});
return $"Task {name} is running on a thread id " +
$"{Thread.CurrentThread.ManagedThreadId}. " +
$"Is thread pool thread:{Thread.CurrentThread.IsThreadPoolThread}";
}
static async Task AsyncProcess()
{
Task<string> t1 = GetInfoAsync("Task 1", 3);
Task<string> t2 = GetInfoAsync("Task 2", 5);
string[] results = await Task.WhenAll(t1, t2);
foreach(string result in results)
{
Console.WriteLine(result);
}
}
static void Main()
{
Task t = AsyncProcess();
t.Wait();
}
}
}
output:
Task 1 first :3
Task 2 first :4
Task 1 second:5
Task 2 second:3
Task Task 1 is running on a thread id 5. Is thread pool thread:True
Task Task 2 is running on a thread id 3. Is thread pool thread:True
处理异步操作中的异常
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace ThreadDemo
{
class Program22
{
static async Task<string> GetInoAsync(string name,int seconds)
{
Console.WriteLine($"GetInfoAsync Pre:{Thread.CurrentThread.ManagedThreadId}");
await Task.Delay(TimeSpan.FromSeconds(seconds));
Console.WriteLine($"GetInfoAsync After:{Thread.CurrentThread.ManagedThreadId}");
throw new Exception($"Boom from {name}!");
//return $"In {Thread.CurrentThread.ManagedThreadId}";
}
static async Task AsyncProcess()
{
Console.WriteLine("1. Single exception");
try
{
Console.WriteLine($"AsyncProcess Pre:{Thread.CurrentThread.ManagedThreadId}");
string result = await GetInoAsync("Task 1", 2);
Console.WriteLine(result);
Console.WriteLine($"AsyncProcess after:{Thread.CurrentThread.ManagedThreadId}");
}
catch (Exception ex)
{
Console.WriteLine($"Exception details:{ex}");
}
Console.WriteLine();
Console.WriteLine("2. Multiple exception");
Console.WriteLine($"Multiple exception Pre:{Thread.CurrentThread.ManagedThreadId}");
Task<string> t1 = GetInoAsync("Task 1", 3);
Task<string> t2 = GetInoAsync("Task 2", 2);
Console.WriteLine($"Multiple await Pre:{Thread.CurrentThread.ManagedThreadId}");
try
{
string[] results = await Task.WhenAll(t1, t2);
Console.WriteLine(results.Length);
}
catch (Exception ex)
{
Console.WriteLine($"Exception details:{ex}");
}
Console.WriteLine();
Console.WriteLine("3. Multiple exceptions witth AggregateExceptioon");
t1 = GetInoAsync("Task 1", 3);
t2 = GetInoAsync("Task 2", 2);
Task<string[]> t3 = Task.WhenAll(t1, t2);
try
{
string[] results = await t3;
Console.WriteLine(results.Length);
}
catch
{
var ae = t3.Exception.Flatten();
var exceptions = ae.InnerExceptions;
Console.WriteLine($"Exceptions caught:{exceptions.Count}");
foreach (var e in exceptions)
{
Console.WriteLine($"Exception details:{e}");
Console.WriteLine();
}
}
Console.WriteLine();
Console.WriteLine("4.await in catch and finally blocks");
try
{
string result = await GetInoAsync("Task 1", 2);
Console.WriteLine(result);
}
catch(Exception ex)
{
await Task.Delay(TimeSpan.FromSeconds(1));
Console.WriteLine($"Catch block with await:Exceptioon details:{ex}");
}
finally
{
await Task.Delay(TimeSpan.FromSeconds(1));
Console.WriteLine("Finally block");
}
}
static void Main()
{
Console.WriteLine($"Main ID:{Thread.CurrentThread.ManagedThreadId}");
Task t = AsyncProcess();
t.Wait();
}
}
}
output:
Main ID:1
1. Single exception
AsyncProcess Pre:1
GetInfoAsync Pre:1
GetInfoAsync After:4
Exception details:System.Exception: Boom from Task 1!
在 ThreadDemo.Program22.<GetInoAsync>d__0.MoveNext() 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program22.cs:行号 16
--- 引发异常的上一位置中堆栈跟踪的末尾 ---
在 System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
在 System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
在 ThreadDemo.Program22.<AsyncProcess>d__1.MoveNext() 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program22.cs:行号 25
2. Multiple exception
Multiple exception Pre:4
GetInfoAsync Pre:4
GetInfoAsync Pre:4
Multiple await Pre:4
GetInfoAsync After:4
GetInfoAsync After:5
Exception details:System.Exception: Boom from Task 1!
在 ThreadDemo.Program22.<GetInoAsync>d__0.MoveNext() 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program22.cs:行号 16
--- 引发异常的上一位置中堆栈跟踪的末尾 ---
在 System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
在 System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
在 ThreadDemo.Program22.<AsyncProcess>d__1.MoveNext() 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program22.cs:行号 41
3. Multiple exceptions witth AggregateExceptioon
GetInfoAsync Pre:5
GetInfoAsync Pre:5
GetInfoAsync After:4
GetInfoAsync After:5
Exceptions caught:2
Exception details:System.Exception: Boom from Task 1!
在 ThreadDemo.Program22.<GetInoAsync>d__0.MoveNext() 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program22.cs:行号 16
--- 引发异常的上一位置中堆栈跟踪的末尾 ---
在 System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
在 System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
在 ThreadDemo.Program22.<AsyncProcess>d__1.MoveNext() 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program22.cs:行号 56
Exception details:System.Exception: Boom from Task 2!
在 ThreadDemo.Program22.<GetInoAsync>d__0.MoveNext() 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program22.cs:行号 16
4.await in catch and finally blocks
GetInfoAsync Pre:5
GetInfoAsync After:4
Catch block with await:Exceptioon details:System.Exception: Boom from Task 1!
在 ThreadDemo.Program22.<GetInoAsync>d__0.MoveNext() 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program22.cs:行号 16
--- 引发异常的上一位置中堆栈跟踪的末尾 ---
在 System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
在 System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
在 ThreadDemo.Program22.<AsyncProcess>d__1.MoveNext() 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program22.cs:行号 74
Finally block
一个常见的错误就是,对一个以上的异步操作使用await
时还使用以上方式。但如果仍像第一种情况一样使用catch代码块,则只能从底层的AggregatetException对象中得到第一个异常,正如第二种情况表现的那样。
为了收集所有异常信息,**可以使用await任务的Exception属性,第三种情况,使用了AggreagateException
的Flatten
方法将层次异常放入一个列表,并从中提取出所有的底层异常。
避免使用捕获的同步上下文
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
namespace ThreadDemo
{
class Program23
{
private static Label _label;
static async void Click(object sender,EventArgs e)
{
_label.Content = new TextBlock() { Text = "Calculating..." };
TimeSpan resultWithContext = await Test();
TimeSpan resultNoContext = await TestNoContext();
//TimeSpan resultNoContext = await TestNoContext().ConfigureAwait(false);
StringBuilder sb = new StringBuilder();
sb.AppendLine($"With the context:{resultWithContext}");
sb.AppendLine($"Without the context:{resultNoContext}");
sb.AppendLine("Ratio:" +
$"{resultWithContext.TotalMilliseconds / resultNoContext.TotalMilliseconds:0.00}");
_label.Content = new TextBlock() { Text = sb.ToString() };
}
[STAThread]
static void Main()
{
Console.WriteLine($"Main Thread ID:{Thread.CurrentThread.ManagedThreadId}");
Application app = new Application();
Window win = new Window();
StackPanel panel = new StackPanel();
Button button = new Button();
_label = new Label();
_label.FontSize = 32;
_label.Height = 200;
button.Height = 200;
button.FontSize = 32;
button.Content = new TextBlock() { Text = "Start async operations" };
button.Click += Click;
panel.Children.Add(_label);
panel.Children.Add(button);
win.Content = panel;
app.Run(win);
Console.ReadLine();
}
static async Task<TimeSpan> Test()
{
const int iterationsNumber = 100000;
var sw = new Stopwatch();
sw.Start();
for(int i = 0; i < iterationsNumber; i++)
{
var t = Task.Run(() =>{ });
await t;
}
sw.Stop();
return sw.Elapsed;
}
static async Task<TimeSpan> TestNoContext()
{
const int iterationsNumber = 100000;
Stopwatch sw = new Stopwatch();
sw.Start();
for(int i = 0; i < iterationsNumber; i++)
{
Task t = Task.Run(() => { });
await t.ConfigureAwait(continueOnCapturedContext: false);
}
sw.Stop();
return sw.Elapsed;
}
}
}
可以看到常规的await操作符花费了更多的时间来完成,这是因为向UI线程中放入了成百上千个后续操作任务,这会使用它的消息循环来异步地执行这些任务,在本例中,我们无需再UI线程中运行该代码,因为异步操作并未访问UI组件。
一个WPF的小例子
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button x:Name="btn" Click="btn_Click" Grid.Row="0">测试</Button>
<TextBlock x:Name="textbox" Width="800" Height="200" Grid.Row="1"/>
</Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApp1
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private async void btn_Click(object sender, RoutedEventArgs e)
{
btn.IsEnabled = false;
string text = "";
for (int i = 0; i < 10; i++)
{
text+="Call ID:" + Thread.CurrentThread.ManagedThreadId+"/n";
//int count=await GetPrimesCountAsync(i * 1000000 + 2, 1000000);
int count = GetPrimesCount(i * 1000000 + 2, 1000000);
text += count.ToString() + "/n";
}
textbox.Text = text;
btn.IsEnabled = true;
}
static Task<int> GetPrimesCountAsync(int start, int count)
{
return Task.Run(() => ParallelEnumerable.Range(start, count).Count(n =>
Enumerable.Range(2, (int)Math.Sqrt(n) - 1).All(i => n % i > 0)));
}
static int GetPrimesCount(int start,int count)
{
return Enumerable.Range(start, count).Count(n =>
Enumerable.Range(2, (int)Math.Sqrt(n) - 1).All(i => n % i > 0));
}
}
}
可以发现,使用await后,点击按钮,并不阻塞主线程,而不使用await,点击按钮,阻塞主线程。
....未完待续