关于.net:关于Async和Await如何工作c#

Regarding how Async and Await works c#

我在这个站点上看到了一些关于异步和等待使用的帖子。很少有人说Async和Await在单独的后台线程上完成其任务意味着生成一个新的后台线程,很少有人说No意味着Async和Await不启动任何单独的后台线程来完成其任务。

所以任何人只要告诉我异步情况下会发生什么,并在使用异步时等待。

这是一个小节目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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;
    }
}

输出为:

1
2
3
Starting Long Running method...
Press any key to exit...
End Long Running method...


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

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

但是,对于IO操作,它依赖IO完成端口在操作完成时发出通知。

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


你们的两个陈述可能都是对的,但都令人困惑。

异步等待通常在单独的后台线程上完成,但并不意味着它启动任何单独的后台线程来完成作业。

这些异步操作的要点是在执行异步操作时不保留线程,因为真正的异步操作不需要线程。

该操作之前的部分可以是CPU绑定的,并且确实需要一个线程,它们由调用线程执行。该操作之后的部分(通常称为完成)也需要一个线程。如果有一个SynchronizationContext(就像在ui或asp.net应用程序中一样)或TaskScheduler,那么这个部分由他们来处理。如果在ThreadPool上没有任何该部分被安排由已经存在的后台线程执行。

因此,在您的示例中,Task.Delay创建一个在5秒后完成的Task。在延迟期间,不需要线程,因此您可以使用AsyncWait。

您的示例流程如下:主线程开始执行Main、调用TestAsyncAwaitMethods、调用LongRunningMethod、打印第一条消息、调用Task.Delay、将方法的其余部分注册为在Task.Delay完成后继续执行、返回Main、打印消息并在ed上同步等待(块)。OCX1〔11〕。

5秒后,Task.Delay中的计时器结束,完成从Task.Delay返回的Task。然后在ThreadPool上安排继续(因为它是一个控制台应用程序)和一个ThreadPool线程,该线程被分配任务打印"结束长时间运行方法…"。

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


理解引擎盖下发生的事情的一个简单方法是使用sharplab,如果粘贴简短的示例,您将了解C编译器是如何重写包含async/await的代码的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
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;
    }
}

正如在许多其他答案中所指出的那样(如该答案),async/await将代码重写为一个状态机,就像用返回IEnumeratorIEnumerableIEnumeratorIEnumerable的方法重写yield语句一样。除async方法外,您可以返回:

  • Task, for an async method that returns a value.
  • Task, for an async method that performs an operation but returns no value.
  • void, for an event handler.
  • Starting with C# 7.0, any type that has an accessible GetAwaiter method. The object returned by the GetAwaiter method must implement the System.Runtime.CompilerServices.ICriticalNotifyCompletion interface.

关于最后一颗子弹,你可以在这里或那里了解更多(事实上它是基于模式的)。这也涉及到其他不在你问题范围内的微妙选择,但是你可以在这里对ValueTaskIValueTaskSource等做一个简短的解释。

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

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

知道在有效的async代码的情况下,这类代码段如下:

1
2
3
4
5
6
7
8
9
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;

大致可以翻译成(更多详情请参见迪信的博客):

1
2
3
4
5
6
7
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=]

few people are saying that Async and Await complete its job on separate background thread means spawn a new background thread and few people are saying no means Async and Await does not start any separate background thread to complete its job.

关于您的代码,您可以跟踪使用的线程(即ID)以及它是否来自池:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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;
    }
}

将输出,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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-最佳实践)

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

关于这些"上下文",我建议您阅读这篇文章,文章将澄清什么是上下文,特别是SynchronizationContext

注意,我说了一个线程池线程来"恢复"而不是执行异步代码,您可以在这里找到更多关于这个的信息。

async方法通常被设计成利用底层调用固有的延迟,通常是IO,例如在磁盘上写、读、在网络上查询等等。

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

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

Void-returning async methods have a specific purpose: to make asynchronous event handlers possible. It is possible to have an event handler that returns some actual type, but that doesn't work well with the language; invoking an event handler that returns a type is very awkward, and the notion of an event handler actually returning something doesn't make much sense.

Event handlers naturally return void, so async methods return void so that you can have an asynchronous event handler. However, some semantics of an async void method are subtly different than the semantics of an async Task or async Task method.

避免这种情况的一种方法是利用C 7.1特性,并期望Task作为返回类型,而不是void

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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;
    }
}

然后你就会得到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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的资源:

  • 迪欣博客:了解C async/await汇编
  • 迪欣博客:了解C async/await(2)等待者模式
  • 迪欣的博客:理解c async/await(3)运行时上下文
  • 史蒂芬·克利里:asyncawait
  • 史蒂芬·克利里:没有线索
  • 史蒂芬·图布:以东x1〔40〕对以东x1〔24〕。

你问错了问题

实际上你在问,包裹是怎么到我家门口的?坐船还是坐飞机?

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

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

一般来说,基于事件的编程比基于线程的编程更高效、更快。这就是大多数.NET API使用它的原因。然而,到目前为止,大多数人都避免了基于事件的编程,因为这是非常难以理解的(同样,为了使这一过程简单化,还设置了async/wait)。


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

只需编译这个并查看ID:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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;
    }

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

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

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