观察线程和任务
- 任务ID:
Task.CurrentId - 线程ID:
Thread.CurrentThread.ManagedThreadId
public static void TraceThreadAndTask(string info)
{
string taskInfo = Task.CurrentId == null ? "no task" : "task " + Task.CurrentId;
Console.WriteLine($"{info} in thread {Thread.CurrentThread.ManagedThreadId} and {taskInfo}");
}
一个同步方法
static string Greeting(string name)
{
TraceThreadAndTask($"running {nameof(Greeting)}");
Task.Delay(3000).Wait();
return $"Hello, {name}";
}
将 同步方法 包装成 异步方法
- 方法名后加上Async后缀
- 返回一个任务
- 此处返回一个返回
string的任务Task<string> - 比较简单的做法是用
Task.Run方法返回一个任务- 泛型版的
Task.Run<string>创建一个返回字符串的任务 - 由于编译器已经知道返回
string,因此可以用Task.Run来简化
- 泛型版的
- 此处返回一个返回
- GreetingAsync方法在一个Task中运行
static Task<string> GreetingAsync(string name) =>
Task.Run(() =>
{
TraceThreadAndTask($"running {nameof(GreetingAsync)}");
return Greeting(name);
});
调用异步方法
这里,异步方法结束后,不会回到调用线程
private static async void CallerWithAsync()
{
TraceThreadAndTask($"started {nameof(CallerWithAsync)}");
string result = await GreetingAsync("Stephanie");
Console.WriteLine(result);
TraceThreadAndTask($"ended {nameof(CallerWithAsync)}");
}
private static async void CallerWithAsync2()
{
TraceThreadAndTask($"started {nameof(CallerWithAsync2)}");
Console.WriteLine(await GreetingAsync("Stephanie"));
TraceThreadAndTask($"ended {nameof(CallerWithAsync2)}");
}
输出:
started CallerWithAsync in thread 1 and no task
running GreetingAsync in thread 3 and task 1
running Greeting in thread 3 and task 1
Hello, Stephanie
//最终没有回到thread1,而是仍然在thread3
ended CallerWithAsync in thread 3 and no task
async 和 await 关键字
async可以放置在如下方法之前:
- 此方法返回值为
void - 此方法返回一个类型,这个类型有
GetAwaiter()方法
await可以等待任何提供GetAwaiter()方法的对象
应避免给返回
void的方法使用async修饰符
使用 Awaiter
Task类的GetAwaiter()返回一个TaskAwaiterTaskAwaiter的OnCompleted()方法,可以指定Task完成时需要做的事情- 这里,用本地函数
OnCompleteAwaiter()来实现Task完成时需要做的事情
这里使用
TaskAwaiter,异步方法结束后,不会回到调用线程
private static void CallerWithAwaiter()
{
TraceThreadAndTask($"starting {nameof(CallerWithAwaiter)}");
TaskAwaiter<string> awaiter = GreetingAsync("Matthias").GetAwaiter();
awaiter.OnCompleted(OnCompleteAwaiter);
void OnCompleteAwaiter()
{
Console.WriteLine(awaiter.GetResult());
TraceThreadAndTask($"ended {nameof(CallerWithAwaiter)}");
}
}
输出:
starting CallerWithAwaiter in thread 1 and no task
running GreetingAsync in thread 3 and task 1
running Greeting in thread 3 and task 1
Hello, Matthias
ended CallerWithAwaiter in thread 3 and no task
延续任务
Task类的ContinueWith()方法定义了任务完成后要调用的代码ContinueWith()方法,入参是一个委托- 这个委托,会将已完成的
Task作为参数传入 - 使用已完成
Task的Result属性,可以访问Task返回的结果
private static void CallerWithContinuationTask()
{
TraceThreadAndTask($"started {nameof(CallerWithContinuationTask)}");
var t1 = GreetingAsync("Stephanie");
t1.ContinueWith(t =>
{
string result = t.Result;
Console.WriteLine(result);
TraceThreadAndTask($"ended {nameof(CallerWithContinuationTask)}");
});
}
输出:
started CallerWithContinuationTask in thread 1 and no task
running GreetingAsync in thread 3 and task 1
running Greeting in thread 3 and task 1
Hello, Stephanie
ended CallerWithContinuationTask in thread 4 and task 2
这里使用
Continuation,后续任务会在另一个线程上,并且不会回到调用线程
同步上下文
- 前例中,在方法的不同声明阶段,使用了不同的线程:
- 对于一个控制台引用程序,这通常没什么问题。
- 但也必须保证,在所有该完成的任务完成之前,至少有一个前台线程仍然在运行。
- 可以通过
Console.ReadLine()来保证主线程一直在运行
- 为了执行某些动作,会需要绑定到指定线程上。例如WPF或Windows应用程序中,只有UI线程才能访问UI元素。这将会是一个问题。
- 对于一个控制台引用程序,这通常没什么问题。
如果使用
async和await关键字,当await完成之后,不需要进行任何特别处理,就能访问UI线程
如果调用异步方法的线程,分配给了同步上下文,那么await完成之后,将继续执行。默认情况下,就是如此,使用了同步上下文。
默认情况下,会把线程,转换到,拥有同步上下文的线程中
- 同步上下文:
- WPF应用程序,有
DispatcherSynchronizationContext属性 - Windows Forms应用程序,有
WindowsFormsSynchronationContext属性 - Windows应用程序,有
WinRTSynchronationContext属性
- WPF应用程序,有
- 有些情况下,例如:
await后面没有用到任何UI元素,这时不按默认行为去切换到同步上下文,反而会提高性能,执行得更加快。因此
如果不需要使用 相同的 同步上下文,则必须显式调用
Task的方法ConfigureAwait(continueOnCapturedContext: false)
使用多个异步方法
- 第二个异步方法 需要等 第一个异步方法 执行完再执行 的实现方式:
private static async void MultipleAsyncMethods()
{
string s1 = await GreetingAsync("Stephanie");
string s2 = await GreetingAsync("Matthias");
Console.WriteLine($"Finished both methods.{Environment.NewLine} Result 1: {s1}{Environment.NewLine} Result 2: {s2}");
}
- 两个异步方法并行执行,等它们都执行完再继续往下执行 的实现方式:
一个组合器:可以接受多个同一类型的参数,并返回同一类型的值。多个同一类型的参数,被组合成一个参数来传递
Task组合器,接受多个Task对象作为参数,并返回一个Task
Task类定义了WhenAll()和WhenAny()组合器
private static async void MultipleAsyncMethodsWithCombinators1()
{
Task<string> t1 = GreetingAsync("Stephanie");
Task<string> t2 = GreetingAsync("Matthias");
await Task.WhenAll(t1, t2);
Console.WriteLine($"Finished both methods.{Environment.NewLine} Result 1: {t1.Result}{Environment.NewLine} Result 2: {t2.Result}");
}
Task类的WhenAll()中,如果所有任务返回相同的类型string, 则用数组string[]来存放await返回的结果
private static async void MultipleAsyncMethodsWithCombinators2()
{
Task<string> t1 = GreetingAsync("Stephanie");
Task<string> t2 = GreetingAsync("Matthias");
string[] result = await Task.WhenAll(t1, t2);
Console.WriteLine($"Finished both methods.{Environment.NewLine} Result 1: {result[0]}{Environment.NewLine} Result 2: {result[1]}");
}
ValueTask: 一种可以用于 等待 的新类型
ValueTask是一个结构- 一般来说
Task在堆上的开销是可以忽略的,因此ValueTask作为结构类型,在堆上开销的优势也不是很大。但也不绝对。
- 一般来说
static async ValueTask<string> GreetingValueTaskAsync(string name)
{
if (names.TryGetValue(name, out string result))
{
return result;
}
else
{
result = await GreetingAsync(name);
names.Add(name, result);
return result;
}
}
下面的例子中,没有使用async和await:
static ValueTask<string> GreetingValueTask2Async(string name)
{
if (names.TryGetValue(name, out string result))
{
return new ValueTask<string>(result);
}
else
{
Task<string> t1 = GreetingAsync(name);
TaskAwaiter<string> awaiter = t1.GetAwaiter();
awaiter.OnCompleted(OnCompletion);
return new ValueTask<string>(t1);
void OnCompletion()
{
names.Add(name, awaiter.GetResult());
}
}
}
转换异步模式
并非.NET Framework中的所有类 都引入了 基于任务的异步模式 的方法,许多类只提供了BeginXXX方法和EndXXX方法。但是可以将后者转换成前者。
- 可以使用
Task.Factory.FromAsync()方法- 第一个参数:
IAsyncResult类型 - 第二个参数:一个委托,这个委托的
- 入参是
IAsyncResult - 返回类型
T由FromAsync<T>()泛型方法指定
- 入参是
- 第一个参数:
private static async void ConvertingAsyncPattern()
{
HttpWebRequest request = WebRequest.Create("http://www.microsoft.com") as HttpWebRequest;
using (WebResponse response = await Task.Factory.FromAsync<WebResponse>(request.BeginGetResponse(null, null), request.EndGetResponse))
{
Stream stream = response.GetResponseStream();
using (var reader = new StreamReader(stream))
{
string content = reader.ReadToEnd();
Console.WriteLine(content.Substring(0, 100));
}
}
}
警告:在旧应用程序中,通常在使用最古早的异步模式时,使用委托的
BeginInvoke()方法。在.NET Core应用程序中使用BeginInvoke()时,编译器不会报错。但是,在运行时,将抛出一个平台不支持的异常
命令行启动.NET Core的dll文件,并给定命令行参数
> dotnet Foundations.dll -async