为了更好地理解编译器的内部运行机制,可以使用SharpLab,如果您将简短的示例粘贴进去,您将了解到C#编译器是如何重写包含async
/await
代码的过程:
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Security.Permissions;
using System.Threading.Tasks;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
internal class Program
{
[CompilerGenerated]
private sealed class <TestAsyncAwaitMethods>d__1 : IAsyncStateMachine
{
public int <>1__state;
public AsyncVoidMethodBuilder <>t__builder;
private TaskAwaiter<int> <>u__1;
private void MoveNext()
{
int num = <>1__state;
try
{
TaskAwaiter<int> awaiter;
if (num != 0)
{
awaiter = LongRunningMethod().GetAwaiter();
if (!awaiter.IsCompleted)
{
num = (<>1__state = 0);
<>u__1 = awaiter;
<TestAsyncAwaitMethods>d__1 stateMachine = this;
<>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
return;
}
}
else
{
awaiter = <>u__1;
<>u__1 = default(TaskAwaiter<int>);
num = (<>1__state = -1);
}
awaiter.GetResult();
}
catch (Exception exception)
{
<>1__state = -2;
<>t__builder.SetException(exception);
return;
}
<>1__state = -2;
<>t__builder.SetResult();
}
void IAsyncStateMachine.MoveNext()
{
this.MoveNext();
}
[DebuggerHidden]
private void SetStateMachine(IAsyncStateMachine stateMachine)
{
}
void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
{
this.SetStateMachine(stateMachine);
}
}
[CompilerGenerated]
private sealed class <LongRunningMethod>d__2 : IAsyncStateMachine
{
public int <>1__state;
public AsyncTaskMethodBuilder<int> <>t__builder;
private TaskAwaiter <>u__1;
private void MoveNext()
{
int num = <>1__state;
int result;
try
{
TaskAwaiter awaiter;
if (num != 0)
{
Console.WriteLine("Starting Long Running method...");
awaiter = Task.Delay(5000).GetAwaiter();
if (!awaiter.IsCompleted)
{
num = (<>1__state = 0);
<>u__1 = awaiter;
<LongRunningMethod>d__2 stateMachine = this;
<>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
return;
}
}
else
{
awaiter = <>u__1;
<>u__1 = default(TaskAwaiter);
num = (<>1__state = -1);
}
awaiter.GetResult();
Console.WriteLine("End Long Running method...");
result = 1;
}
catch (Exception exception)
{
<>1__state = -2;
<>t__builder.SetException(exception);
return;
}
<>1__state = -2;
<>t__builder.SetResult(result);
}
void IAsyncStateMachine.MoveNext()
{
this.MoveNext();
}
[DebuggerHidden]
private void SetStateMachine(IAsyncStateMachine stateMachine)
{
}
void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
{
this.SetStateMachine(stateMachine);
}
}
private static void Main(string[] args)
{
TestAsyncAwaitMethods();
Console.WriteLine("Press any key to exit...");
Console.ReadLine();
}
[AsyncStateMachine(typeof(<TestAsyncAwaitMethods>d__1))]
[DebuggerStepThrough]
public static void TestAsyncAwaitMethods()
{
<TestAsyncAwaitMethods>d__1 stateMachine = new <TestAsyncAwaitMethods>d__1();
stateMachine.<>t__builder = AsyncVoidMethodBuilder.Create();
stateMachine.<>1__state = -1;
AsyncVoidMethodBuilder <>t__builder = stateMachine.<>t__builder;
<>t__builder.Start(ref stateMachine);
}
[AsyncStateMachine(typeof(<LongRunningMethod>d__2))]
[DebuggerStepThrough]
public static Task<int> LongRunningMethod()
{
<LongRunningMethod>d__2 stateMachine = new <LongRunningMethod>d__2();
stateMachine.<>t__builder = AsyncTaskMethodBuilder<int>.Create();
stateMachine.<>1__state = -1;
AsyncTaskMethodBuilder<int> <>t__builder = stateMachine.<>t__builder;
<>t__builder.Start(ref stateMachine);
return stateMachine.<>t__builder.Task;
}
}
正如在SO上其他答案中指出的那样(例如这个),async
/ await
将代码重写为状态机,就像使用返回IEnumerator
、IEnumerable
、IEnumerator<T>
、IEnumerable<T>
的方法一样。不同之处在于,对于async
方法,你可以返回任何一个:
关于最后一个弹药,您可以在这里
这里和
那里阅读更多关于它(即它是基于模式的)的信息。这还涉及其他微妙的选择,超出了您问题的范围,但您可以在这里
这里有关ValueTask<TResult>
,IValueTaskSource<TResult>
等的简短说明。
代码重写的行为被委托给编译器,Roslyn基本上使用
AsyncRewriter
类来了解如何重写不同的执行路径和分支以获得等效的代码。
在包含
yield
或
async
关键字的有效代码中,无论哪种情况,都有一个初始状态,并且根据分支和执行路径,背后发生的
MoveNext()
调用将从一个状态转移到另一个状态。
在了解到有效的
async
代码的情况下,下面这种片段:
case -1:
HelperMethods.Before();
this.awaiter = AsyncMethods.MethodAsync(this.Arg0, this.Arg1).GetAwaiter();
if (!this.awaiter.IsCompleted)
{
this.State = 0;
this.Builder.AwaitUnsafeOnCompleted(ref this.awaiter, ref this);
}
break;
可以大致翻译为(详见Dixin的博客):
case -1:
HelperMethods.Before();
this.currentTaskToAwait = AsyncMethods.MethodAsync(this.Arg0, this.Arg1);
this.State = 0;
this.currentTaskToAwait.ContinueWith(_ => that.MoveNext());
break;
请记住,如果您将
void
作为
async
方法的返回类型,则不会有太多
currentTaskToAwait
=]。
有些人说Async和Await在单独的后台线程上完成其工作,这意味着会产生一个新的后台线程,而有些人则认为Async和Await不会启动任何单独的后台线程来完成其工作。
关于您的代码,您可以跟踪使用哪个线程(即ID)以及它是否来自池:
public static class Program
{
private static void DisplayCurrentThread(string prefix)
{
Console.WriteLine($"{prefix} - Thread Id: {Thread.CurrentThread.ManagedThreadId}");
Console.WriteLine($"{prefix} - ThreadPool: {Thread.CurrentThread.IsThreadPoolThread}");
}
public static void Main(params string[] args)
{
DisplayCurrentThread("Main Pre");
TestAsyncAwaitMethods();
DisplayCurrentThread("Main Post");
Console.ReadLine();
}
private static async void TestAsyncAwaitMethods()
{
DisplayCurrentThread("TestAsyncAwaitMethods Pre");
await LongRunningMethod();
DisplayCurrentThread("TestAsyncAwaitMethods Post");
}
private static async Task<int> LongRunningMethod()
{
DisplayCurrentThread("LongRunningMethod Pre");
Console.WriteLine("Starting Long Running method...");
await Task.Delay(500);
Console.WriteLine("End Long Running method...");
DisplayCurrentThread("LongRunningMethod Post");
return 1;
}
}
将输出例如:
Main Pre - Thread Id: 1
Main Pre - ThreadPool: False
TestAsyncAwaitMethods Pre - Thread Id: 1
TestAsyncAwaitMethods Pre - ThreadPool: False
LongRunningMethod Pre - Thread Id: 1
LongRunningMethod Pre - ThreadPool: False
Starting Long Running method...
Main Post - Thread Id: 1
Main Post - ThreadPool: False
End Long Running method...
LongRunningMethod Post - Thread Id: 4
LongRunningMethod Post - ThreadPool: True
TestAsyncAwaitMethods Post - Thread Id: 4
TestAsyncAwaitMethods Post - ThreadPool: True
你可以注意到,
LongRunningMethod
方法在
Main
方法之后终止,这是因为你将异步方法的返回类型设置为
void
。只有事件处理程序才应该使用
async void
方法(请参见
Async/Await - Best Practices in Asynchronous Programming)。
另外,正如 i3arnon 已经提到的那样,由于没有传递上下文,因此程序确实会从线程池中重用线程以在异步方法调用后恢复执行。
关于这些“上下文”,我建议你阅读
那篇文章,这篇文章将澄清什么是上下文,特别是
SynchronizationContext
。
注意,我说的是线程池线程“恢复”,而不是执行异步代码块,您可以在
这里找到更多信息。
异步方法通常旨在利用底层调用固有的任何延迟,通常是IO,例如写入、读取磁盘上的某些内容、查询网络上的某些内容等等。
真正异步方法的目的是避免使用线程进行IO操作,这可以帮助应用程序在有更多请求时扩展。通常可以使用
async
资源来处理更多的ASP.NET WebAPI请求,因为每个请求的线程在它们击中数据库或其他您正在进行的
async
调用时将被“释放”。
我建议您阅读
此问题的答案。
无返回值的异步方法有一个特定的用途:使异步事件处理程序成为可能。虽然可以有返回某种实际类型的事件处理程序,但这与语言不兼容;调用返回类型的事件处理程序非常笨拙,并且事件处理程序实际上返回某些东西的概念并不太合理。
事件处理程序自然返回void,因此异步方法返回void,以便您可以拥有异步事件处理程序。但是,异步void方法的一些语义与异步Task或异步Task方法的语义略有不同。
避免这种情况的一种方法是利用
C# 7.1 feature 并期望将
void
作为返回类型改为
Task
:
public static class Program
{
private static void DisplayCurrentThread(string prefix)
{
Console.WriteLine($"{prefix} - Thread Id: {Thread.CurrentThread.ManagedThreadId}");
Console.WriteLine($"{prefix} - ThreadPool: {Thread.CurrentThread.IsThreadPoolThread}");
}
public static async Task Main(params string[] args)
{
DisplayCurrentThread("Main Pre");
await TestAsyncAwaitMethods();
DisplayCurrentThread("Main Post");
Console.ReadLine();
}
private static async Task TestAsyncAwaitMethods()
{
DisplayCurrentThread("TestAsyncAwaitMethods Pre");
await LongRunningMethod();
DisplayCurrentThread("TestAsyncAwaitMethods Post");
}
private static async Task<int> LongRunningMethod()
{
DisplayCurrentThread("LongRunningMethod Pre");
Console.WriteLine("Starting Long Running method...");
await Task.Delay(500);
Console.WriteLine("End Long Running method...");
DisplayCurrentThread("LongRunningMethod Post");
return 1;
}
}
然后你会得到
Main Pre - Thread Id: 1
Main Pre - ThreadPool: False
TestAsyncAwaitMethods Pre - Thread Id: 1
TestAsyncAwaitMethods Pre - ThreadPool: False
LongRunningMethod Pre - Thread Id: 1
LongRunningMethod Pre - ThreadPool: False
Starting Long Running method...
End Long Running method...
LongRunningMethod Post - Thread Id: 4
LongRunningMethod Post - ThreadPool: True
TestAsyncAwaitMethods Post - Thread Id: 4
TestAsyncAwaitMethods Post - ThreadPool: True
Main Post - Thread Id: 4
Main Post - ThreadPool: True
这更符合您通常的期望。
关于 async
/ await
的更多资源: