关于Async和Await如何工作c#

时间:2015-08-21 09:34:22

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

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

7 个答案:

答案 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语句返回IEnumeratorIEnumerableIEnumerator<T>IEnumerable<T>的方法。除async方法外,您可以返回either

  

关于最后一个项目符号,您可以详细了解herethere(基于模式)。这还涉及其他超出您问题范围的微妙选择,但您可以简短说明here about ValueTask<TResult>, IValueTaskSource<TResult>, etc.

代码的重写行为委托给编译器,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;

可以粗略地翻译成(有关更多详细信息,请参见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的更多资源:

答案 4 :(得分:0)

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

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

为了向你提供一个Task,你正在等待的方法可能会产生一个新线程来完成它的工作,它可以同步返回一个已完成的任务和一个值(从结果中创建一个任务),它可以做任何想做的事情。除非您从等待方法收到的Task对象完成,否则所有等待都会将控件返回给您的函数的父级。此时它将继续从await行继续执行您的方法。

答案 5 :(得分:0)

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

只需编译并查看Id的:

sudo

答案 6 :(得分:0)

最简单的解决方案是

await LongRunningMethod().wait();

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