【问题标题】:Regarding how Async and Await works c#关于 Async 和 Await 如何工作 c#
【发布时间】:2015-11-15 04:58:05
【问题描述】:

我在这个网站上看到了一些关于 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...

【问题讨论】:

  • @Mou:你可以通过isbackground属性检查自己是后台线程还是UI线程。
  • 你认为它启动了一个后台线程吗?
  • isbackground 是线程类的属性....我如何将它与 async 和 await 关键字一起使用。抱歉不知道。你能更详细一点吗?谢谢
  • This 将回答您的所有问题。通过它。简而言之,async-await 不使用任何线程。是否使用线程取决于方法的实现。还有this
  • 自己找找,用调试器。使用断点和 Debug > Windows > Threads 调试器窗口。反正你迟早会需要它,最好现在就熟悉它。

标签: c# .net async-await task-parallel-library


【解决方案1】:

最简单的解决方案是,

await LongRunningMethod().wait();

这将导致主线程等待(非阻塞)直到LongRunningMethod 完成执行。

【讨论】:

    【解决方案2】:

    了解幕后情况的一种简单方法是使用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 语句一样,其方法返回 IEnumeratorIEnumerableIEnumerator&lt;T&gt;IEnumerable&lt;T&gt;。除了async方法,可以返回either

    关于最后一个项目符号,您可以阅读更多信息(事实上它是基于模式的)herethere。这也涉及到您的问题范围之外的其他微妙选择,但您可以有一个简短的解释here about ValueTask&lt;TResult&gt;, IValueTaskSource&lt;TResult&gt;, etc.

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

    在这两种情况下,如果您有一个包含 yieldasync 关键字的有效代码,您就有一个初始状态,并且取决于分支、执行路径,在幕后发生的 MoveNext() 调用将从一个状态移动到另一个。

    知道在有效async的情况下,下面的这种sn-p代码:

    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;
    

    大致可以翻译成(详见迪信的博客):

    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,以便您可以拥有异步事件处理程序。但是,async void 方法的某些语义与 async Task 或 async 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的资源:

    【讨论】:

    • 是的,阿姨,是的,这就是我要找的。谢谢。
    【解决方案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.Delay 返回的Task。然后将继续安排在ThreadPool(因为它是一个控制台应用程序)和分配给该任务的ThreadPool线程打印“End Long Running method...”。

    总之,真正的异步操作不需要线程才能运行,但它完成后确实需要一个线程,通常是来自ThreadPool 的后台线程,但不一定。

    【讨论】:

    • 感谢您的回复。你说“异步等待通常在单独的后台线程上完成,但这并不意味着它启动任何单独的后台线程来完成工作”你说异步等待在单独的线程上完成它的工作,但它不会创建或启动任何线程然后谁以及如何 bg 线程进入 Async-await 工作或完成其工作的场景。有点混乱。
    • @牟ThreadPool。除非有 SynchronizationContextTaskScheduler 在异步操作完成时以其他方式运行,否则将在 ThreadPool 上安排其继续。所以一个后台线程被挖掘并返回,但不用于实际操作。
    • 抱歉不清楚你想说什么。你能详细说明一下吗?
    • @Mou 一个真正的异步操作,例如Task.Delay 中的延迟,不需要线程来运行,但如果您有一个带有await Task.Delay(1000); 的异步方法以及之后的代码,那么这段代码需要一些线程继续运行。除非另有说明,否则该线程将是 ThreadPool 线程。这就是为什么说 async-await 不创建后台线程并说 async-await 在后台线程上完成是正确的。
    【解决方案4】:

    需要了解两件事:a) async/await 使用任务(任务使用线程池)b) async/await 不适用于并行工作。

    只需编译它并查看 ID:

    static void Main(string[] args)
        {
            Console.WriteLine("Id main thread is: {0}", Thread.CurrentThread.ManagedThreadId);
            TestAsyncAwaitMethods();
            Console.WriteLine("Press any key to exit...");
            Console.ReadLine();
        }
    
        public async static void TestAsyncAwaitMethods()
        {
            Console.WriteLine("Id thread (void - 0) is: {0}", Thread.CurrentThread.ManagedThreadId);
            var _value = await LongRunningMethod();
            Console.WriteLine("Id thread (void - 1) is: {0}", Thread.CurrentThread.ManagedThreadId);
        }
    
        public static async Task<int> LongRunningMethod()
        {
            Console.WriteLine("Id thread (int) is: {0}", Thread.CurrentThread.ManagedThreadId);
            Console.WriteLine("Starting Long Running method...");
            await Task.Delay(1000);
            Console.WriteLine("End Long Running method...");
            return 1;
        }
    

    【讨论】:

      【解决方案5】:

      你问错问题了

      实际上,您是在问,包裹如何送到我家门口?坐船还是坐飞机?

      关键是您的门阶不在乎包裹是通过海运还是空运。

      然而,微软开发 Task/async/await 框架的主要原因是利用基于事件的编程而不是基于线程的编程。

      一般来说,基于事件的编程比基于线程的编程更高效、更快。这就是大多数 .net API 使用它的原因。然而,到目前为止,大多数人都避免使用基于事件的编程,因为它非常难以理解(再次,为了简化这一点,我们使用了异步/等待)。

      【讨论】:

      • 为什么你认为异步/等待与基于事件的编程有关?什么事件与异步/等待相关联? bgWorker 是我所知道的基于事件的编程。解释更多以证明 async/wait 是基于事件的编程。
      • @Mou 我不是指 EAP 模式编程。我的意思是异步是通过回调和中断来实现的。
      • 你能否提供回调和中断的示例代码。
      【解决方案6】:

      只能在标记为异步的方法中调用 await。一旦您等待一个函数,框架就知道如何记住您当前的调用环境,并在等待的函数完成后将控制权返回给它。

      您只能等待返回任务的函数。所以所有 await 处理的是返回的 Task 对象(并且在返回任务之前,您正在等待的方法正在同步执行)

      为了给你提供一个任务,你正在等待的方法可以生成一个新线程来完成它的工作,它可以同步返回一个带有值的已完成任务(从结果创建一个任务),它可以做任何它想做的事情.所有 await 所做的就是将控制权交还给您的函数的父级,直到并且除非您从可等待方法收到的 Task 对象完成。届时,它将继续从 await 行执行您的方法。

      【讨论】:

        【解决方案7】:

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

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

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

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

        【讨论】:

        • 你想说 Task.Run 产生新的 bg 线程,但 async/await 没有启动任何线程......我说得对吗?
        • 我很确定有例外,但这是一个合理的基本假设。
        • 您能否列出使用异步/等待的优点。因为对于其他方式的异步编程,例如 bgworker、task.run、线程池和线程类。人们在什么样的场景中使用 async/await。如果时间允许,请回答。谢谢
        • 这接近于对话,这是一个堆栈溢出诺诺。 bgworker 和 task.run 都使用线程池(使用线程)。 async/await 与 bgworker 共享异步,但提供了更熟悉的编程体验。
        • 你说“async/await 与 bgworker 共享异步”后台 worker 是不同的类......那么 async/await 与后台 worker 类共享的关系是什么?
        猜你喜欢
        • 1970-01-01
        • 2018-11-26
        • 1970-01-01
        • 2021-12-18
        • 1970-01-01
        • 2021-05-12
        • 1970-01-01
        • 2019-12-04
        相关资源
        最近更新 更多