我在本网站上看到一些关于Async和Await使用的帖子。很少有人说Async和Await在单独的后台线程上完成它的工作意味着产生一个新的后台线程,很少有人说没有意味着Async和Await没有启动任何单独的后台线程来完成它的工作。
所以任何人只要告诉我在使用Async和Await时会发生什么。
class Program
{
static void Main(string[] args)
{
TestAsyncAwaitMethods();
Console.WriteLine("Press any key to exit...");
Console.ReadLine();
}
public async static void TestAsyncAwaitMethods()
{
await LongRunningMethod();
}
public static async Task<int> LongRunningMethod()
{
Console.WriteLine("Starting Long Running method...");
await Task.Delay(5000);
Console.WriteLine("End Long Running method...");
return 1;
}
}
输出是:
Starting Long Running method...
Press any key to exit...
End Long Running method...
答案 0 :(得分:7)
问题是async/await
与异步有关,而非线程。
如果使用Task.Run
,它确实会使用后台线程(通过线程池,通过任务并行库)。
但是,对于IO操作,它依赖于IO完成端口来通知操作何时完成。
唯一保证async/await
使得当一个操作完成时,它将在它开始时在那里的SynchronizationContext中返回给你的调用者。实际上,这意味着它将返回UI线程(在Windows应用程序中)或可以返回HTTP响应的线程(在ASP.NET中)
答案 1 :(得分:3)
你的两个陈述都可能是真的,但令人困惑。
Async-await通常在单独的后台线程上完成,但它并不意味着它启动任何单独的后台线程来完成工作。
这些异步操作的目的是在执行异步操作时不保留线程,因为真正的异步操作不需要线程。
该操作之前的部分可以是CPU绑定的,并且需要一个线程并且它们由调用线程执行。该操作之后的部件(通常称为完成)也需要一个线程。如果有SynchronizationContext
(就像在UI或asp.net应用程序中那样)或TaskScheduler
那么该部分由他们处理。如果没有任何部分在ThreadPool
上安排由现有的后台线程执行。
因此,在您的示例Task.Delay
中创建一个{5}后完成的Task
。在那段延迟期间,不需要线程,因此您可以使用async-await。
您的示例流程如下:主线程开始执行Main
,调用TestAsyncAwaitMethods
,调用LongRunningMethod
,打印第一条消息,调用Task.Delay
,注册该方法的其余部分作为Task.Delay
完成后继续执行,返回Main
,打印消息并在Console.ReadLine
上同步等待(块)。
5秒后,Task.Delay
中的计时器结束并完成从Task
返回的Task.Delay
。然后在ThreadPool
(因为它是一个控制台应用程序)和一个ThreadPool
线程上安排继续,该线程已分配该任务打印&#34;结束长时间运行方法......&#34 ;
总之,真正的异步操作并不需要一个线程能够运行,但它在完成后需要一个线程,这通常是ThreadPool
的后台线程,但不一定
答案 2 :(得分:1)
实际上你在问,一个包裹怎么到我家门口?通过船或飞机?
重点是你的门步不关心包裹是通过海运还是空运。
然而,微软开发Task / async / await框架的主要原因是利用基于事件的编程而不是基于线程的编程。
一般而言,基于事件的编程比基于线程的编程更有效,更快。这就是大多数.net API使用它的原因。到目前为止,大多数人都避免使用基于事件的编程,因为它非常难以理解(同样,async / wait也是为了使这一点变得简单)。
答案 3 :(得分:1)
了解幕后情况的一种简单方法是使用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()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
this.MoveNext();
}
[DebuggerHidden]
private void SetStateMachine(IAsyncStateMachine stateMachine)
{
}
void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
{
//ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
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()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
this.MoveNext();
}
[DebuggerHidden]
private void SetStateMachine(IAsyncStateMachine stateMachine)
{
}
void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
{
//ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
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上许多其他答案(如that one)所指出的,async
/ await
将代码重写为状态机,就像使用yield
语句返回IEnumerator
,IEnumerable
,IEnumerator<T>
,IEnumerable<T>
的方法。除async
方法外,您可以返回either:
Task<TResult>
,用于返回值的异步方法。Task
,用于执行操作但不返回值的异步方法。void
,用于事件处理程序。- 从C#7.0开始,任何具有可访问的
GetAwaiter
方法的类型。GetAwaiter
方法返回的对象必须实现System.Runtime.CompilerServices.ICriticalNotifyCompletion接口。
关于最后一个项目符号,您可以详细了解here和there(基于模式)。这还涉及其他超出您问题范围的微妙选择,但您可以简短说明here about ValueTask<TResult>
, IValueTaskSource<TResult>
, etc.
代码的重写行为委托给编译器,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: // -1 is begin.
HelperMethods.Before(); // Code before 1st await.
this.currentTaskToAwait = AsyncMethods.MethodAsync(this.Arg0, this.Arg1); // 1st task to await
// When this.currentTaskToAwait is done, run this.MoveNext() and go to case 0.
this.State = 0;
this.currentTaskToAwait.ContinueWith(_ => that.MoveNext()); // Callback
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所提到的,由于没有传递任何上下文,因此该程序确实(重新)使用了线程池中的线程来在异步方法调用之后恢复其执行。
关于那些“上下文”,我建议您阅读that article,本文将阐明什么是上下文,尤其是SynchronizationContext
。
请注意,我说过一个线程池线程可以“恢复”而不是执行异步代码,您可以了解有关here的更多信息。
async
方法通常设计为利用底层调用固有的延迟,例如,通常是IO。编写,读取磁盘上的内容,通过网络查询内容等等。
真正的异步方法的目的是避免将线程用于IO东西,这些线程可以帮助您在有更多请求时扩展应用程序。通常,它们可以使用async
资源处理ASP.NET WebAPI中的更多请求,因为它们每当它们访问数据库或进行任何async
可调用的操作时,都会“释放”请求线程该资源。
我建议您阅读该question
的答案返回无效的异步方法有一个特定目的:使异步事件处理程序成为可能。可能有一个返回实际类型的事件处理程序,但不适用于该语言。调用返回类型的事件处理程序非常尴尬,而事件处理程序实际上返回某些东西的概念没有多大意义。
事件处理程序自然会返回void,因此异步方法将返回void,以便您可以拥有异步事件处理程序。但是,异步void方法的某些语义与异步Task方法或异步Task方法的语义稍有不同。
避免这种情况的一种方法是利用C# 7.1 feature并期望Task
作为返回类型而不是void
:
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
的更多资源:
async
/ await
(1) Compilation async
/ await
(2) Awaitable-Awaiter Pattern async
/ await
(3) Runtime Context async
and await
ExecutionContext
vs SynchronizationContext
答案 4 :(得分:0)
只能在标记为async的方法内调用await。等待函数后,框架知道如何记住当前的调用环境,并在等待的函数完成后将控制权返回给它。
您只能等待返回任务的函数。因此,所有等待处理的是返回的Task对象(并且在返回任务之前,您正在等待的方法是同步执行的)
为了向你提供一个Task,你正在等待的方法可能会产生一个新线程来完成它的工作,它可以同步返回一个已完成的任务和一个值(从结果中创建一个任务),它可以做任何想做的事情。除非您从等待方法收到的Task对象完成,否则所有等待都会将控件返回给您的函数的父级。此时它将继续从await行继续执行您的方法。
答案 5 :(得分:0)
需要了解两件事:a)异步/等待使用任务(任务使用线程池)b)async / await不用于并行工作。
只需编译并查看Id的:
sudo
答案 6 :(得分:0)
最简单的解决方案是
await LongRunningMethod().wait();
这将导致主线程等待(不阻塞),直到LongRunningMethod
执行完毕。