关于Async和Await如何工作c#

Mou*_*Mou 3 .net c# task-parallel-library async-await

我在这个网站上看到了一些关于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;
    }
}
Run Code Online (Sandbox Code Playgroud)

输出是:

Starting Long Running method...
Press any key to exit...
End Long Running method...
Run Code Online (Sandbox Code Playgroud)

Ric*_*lay 7

问题是async/await关于异步,而不是线程.

如果你使用Task.Run,它确实会使用后台线程(通过线程池,通过任务并行库).

但是,对于IO操作,它依赖IO完成端口来通知操作何时完成.

唯一的保证async/await是,当一个操作完成时,它将在它开始时在那里的SynchronizationContext中返回给你的调用者.实际上,这意味着它将返回UI线程(在Windows应用程序中)或可以返回HTTP响应的线程(在ASP.NET中)


Eho*_*ret 6

了解幕后发生的事情的一种简单方法是使用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;
    }
}
Run Code Online (Sandbox Code Playgroud)

正如关于 SO 的许多其他答案所指出的(就像那个答案),async/await将代码重写为状态机,就像yield使用返回IEnumerator, IEnumerable, IEnumerator<T>,的方法的语句一样IEnumerable<T>。除了对async方法,就可以返回两种

关于最后一个项目符号,您可以在这里那里阅读更多关于它的信息(它是基于模式的事实)。这也涉及到其他微妙的选择,是你的问题的范围了,但你可以有一个简短的说明这里有关ValueTask<TResult>IValueTaskSource<TResult>等等。

代码重写的行为委托给编译器,Roslyn 基本上是使用AsyncRewriter类来知道如何重写不同的执行路径,分支以获得等效的代码。

在两种情况下,如果您有包含yieldasync关键字的有效代码,您就有一个初始状态,并且根据分支、执行路径,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;
Run Code Online (Sandbox Code Playgroud)

大致可以翻译成(更多细节见狄鑫的博客):

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;
Run Code Online (Sandbox Code Playgroud)

请记住,如果你有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;
    }
}
Run Code Online (Sandbox Code Playgroud)

将输出例如:

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
Run Code Online (Sandbox Code Playgroud)

您可以注意到该LongRunningMethod方法Main方法之后终止,这是因为您用作void异步方法的返回类型。一个async void方法应该只用于事件处理程序而不是其他任何东西(请参阅Async/Await - 异步编程中的最佳实践

此外,正如 i3arnon 已经提到的,由于没有传递上下文,是的,程序确实(重新)使用线程池中的线程在异步方法调用后恢复其执行。

关于那些“上下文”,我建议您阅读那篇文章,该文章将阐明什么是上下文,尤其是SynchronizationContext.

请注意,我说线程池线程是“恢复”而不是执行异步代码段,您可以在此处找到更多相关信息。

async方法通常旨在利用底层调用固有的任何延迟,通常是 IO,例如。写入、读取磁盘上的内容、通过网络查询内容等等。

真正异步方法的目的是避免将线程用于 IO 内容,这可以在您有更多请求时帮助应用程序扩展。通常可以使用async资源在 ASP.NET WebAPI 中处理更多请求,因为它们中的每一个都将“释放”请求的线程,只要它们访问数据库或async您在该资源中进行的任何调用。

我建议你阅读那个问题的答案

返回空值的异步方法有一个特定的目的:使异步事件处理程序成为可能。可能有一个事件处理程序返回一些实际类型,但这在语言中不能很好地工作;调用返回类型的事件处理程序非常笨拙,并且事件处理程序实际上返回某些内容的概念没有多大意义。

事件处理程序自然返回 void,因此异步方法返回 void,以便您可以拥有异步事件处理程序。但是,async void 方法的某些语义与 async Task 或 async Task 方法的语义略有不同。

避免这种情况的一种方法是利用C# 7.1 功能并期望 aTask作为返回类型而不是void

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
Run Code Online (Sandbox Code Playgroud)

然后你会得到

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
Run Code Online (Sandbox Code Playgroud)

这看起来更像您通常期望的那样。

关于async/ 的更多资源await