Unity异步详解

异步(Async)底层如何实现

example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class AsyncTest
{
async void Start()
{
Debug.Log("Start");

await AsyncMethod();

Debug.Log("End");
}

async Task AsyncMethod()
{
Debug.Log("Async Method Start");

await Task.Delay(1000);

Debug.Log("Async Method End");
}
}

异步与协程

  1. 协程(Coroutine)只是模拟并发,实质上是在主线程利用时间片轮转机制
  2. 异步(Async)借助线程池做到真正意义上的并发
  3. 非异步编程在执行一个任务时线程会一直等待该任务完成,直到返回结果才能执行后面的内容,这种方式会阻塞线程,无法充分利用CPU;异步编程则意味着在执行一个任务时,线程不会等待当前任务完成,而是立即执行下一个任务,而当前任务完成后会通知线程并执行回调函数,避免线程阻塞,提高性能。

关键字

  • async关键字标记一个函数是异步函数
  • await关键字来暂停函数的执行并等待一个任务结果的完成

Task

  1. Task实际上才是C#异步编程的主要类
  • Task.Run(Action):将指定的操作包装成一个 Task 对象,并提交到线程池中执行。该方法会返回一个 Task 对象,可以使用 await 关键字等待其执行完成。
  • Task.Delay(int):创建一个延迟指定时间的 Task 对象。该方法会返回一个 Task 对象,可以使用 await 关键字等待其延迟完成。
  • Task.WhenAll(params Task[]):等待指定的所有 Task 对象执行完成。该方法会返回一个 Task 对象,可以使用 await 关键字等待所有 Task 对象完成。
  • Task.WhenAny(params Task[]):等待指定的任意一个 Task 对象执行完成。该方法会返回一个 Task 对象,可以使用 await 关键字等待任意一个 Task 对象完成。
  • Task.Result:获取 Task 对象的执行结果。如果 Task 对象还没有执行完成,该属性会阻塞当前线程,直到 Task 对象执行完成。
  • Task.Status:获取 Task 对象的执行状态。该属性返回一个枚举值,表示 Task 对象的执行状态,例如 Running、Canceled、Faulted 等。
  • Task.Exception:获取 Task 对象的异常信息。如果 Task 对象执行过程中发生了异常,该属性会返回一个 AggregateException 对象,其中包含了所有的异常信息。

线程安全

  1. MonoBehaviour是单线程模型,开新线程会引发线程安全问题?
  2. 实际上做一些外部DLL的调用或不涉及MonoBehaviour类的操作,是可以运行在另外的线程的,但只要涉及依赖MonoBehaviour,Unity是不允许这样操作的
  3. 因为这个原因,Unity中异步操作一般实现网络传输,资源管理等较少的功能

异步与状态机

  1. 执行完成 ← 运行中(占用资源) ↔ 暂停中(释放资源)
  2. Task对象在底层是通过状态机实现的。编译器会隐式将Task对象转化为状态机。Task对象有多个状态,比如运行中(Running)、暂停(Suspended)、完成(Completed)等。执行Task的内部任务时,Task对象进入运行中状态。遇到await表达式时会暂停Task对象,状态变为暂停,这个过程会释放线程资源,让其他代码运行。Task对象的任务完成后,状态机会自动恢复该Task对象的运行状态。异常也会作为状态机的“错误”状态来捕获。Task对象可以包含多个await,多次变换运行-暂停状态。库函数比如Task.Run会安排状态之间的切换。
  3. 执行上下文(Execution Context)
  • 当函数被调用时,会创建一个新的执行上下文压到调用栈上。函数执行完成后,也就是return返回值之后,上下文会从栈中移除。当await暂停一个异步函数时,它的执行上下文仍然存在于调用栈中。等暂停的操作完成后,会使用同一个上下文恢复函数的执行。这保证了函数在暂停和恢复时能够访问到它执行期间的所有重要信息。
  • 协程也类似,协程启动时注册到一个队列,遇到yield关键字,协程挂起,但执行上下文仍然在栈中,Unity引擎每帧从等待队列取出一个IEnumerator进行执行,执行到上次yield点继续向后执行,调度循环如此不断交替不同协程的执行。

反编译&解析

  1. C#
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class Async : MonoBehaviour
    {
    private void Update()
    {
    if (Input.GetKeyDown(KeyCode.A))
    {
    Task.Run(() => { StartWork(); });
    }
    }
    private async void StartWork()
    {
    UnityEngine.Debug.Log("start.."+ Thread.CurrentThread.ManagedThreadId);
    await DoWork();
    UnityEngine.Debug.Log("end.."+ Thread.CurrentThread.ManagedThreadId);
    }
    private async Task DoWork()
    {
    UnityEngine.Debug.Log("task start.." + Thread.CurrentThread.ManagedThreadId);
    Task task = Task.Delay(1000);
    await task;
    UnityEngine.Debug.Log("task end.."+ Thread.CurrentThread.ManagedThreadId);
    }
    }
  2. CIL
    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
    public class Async : MonoBehaviour
    {
    private void Update()
    {
    if (!Input.GetKeyDown(KeyCode.A))
    return;
    // ISSUE: method pointer
    Task.Run(new Action((object) this, __methodptr(<Update>b__1_0)));
    }

    [AsyncStateMachine(typeof (Async.<StartWork>d__2))]
    [DebuggerStepThrough]
    private void StartWork()
    {
    Async.<StartWork>d__2 stateMachine = new Async.<StartWork>d__2();
    stateMachine.<>4__this = this;
    stateMachine.<>t__builder = AsyncVoidMethodBuilder.Create();
    stateMachine.<>1__state = -1;
    stateMachine.<>t__builder.Start<Async.<StartWork>d__2>(ref stateMachine);
    }

    [AsyncStateMachine(typeof (Async.<DoWork>d__3))]
    [DebuggerStepThrough]
    private Task DoWork()
    {
    Async.<DoWork>d__3 stateMachine = new Async.<DoWork>d__3();
    stateMachine.<>4__this = this;
    stateMachine.<>t__builder = AsyncTaskMethodBuilder.Create();
    stateMachine.<>1__state = -1;
    stateMachine.<>t__builder.Start<Async.<DoWork>d__3>(ref stateMachine);
    return stateMachine.<>t__builder.Task;
    }

    public Async()
    {
    base..ctor();
    }

    [CompilerGenerated]
    private void <Update>b__1_0()
    {
    this.StartWork();
    }

    [CompilerGenerated]
    private sealed class <StartWork>d__2 : IAsyncStateMachine
    {
    public int <>1__state;
    public AsyncVoidMethodBuilder <>t__builder;
    public Async <>4__this;
    private TaskAwaiter <>u__1;

    public <StartWork>d__2()
    {
    base..ctor();
    }

    void IAsyncStateMachine.MoveNext()
    {
    int num1 = this.<>1__state;
    try
    {
    TaskAwaiter awaiter;
    int num2;
    if (num1 != 0)
    {
    UnityEngine.Debug.Log((object) ("start.." + (object) Thread.CurrentThread.ManagedThreadId));
    awaiter = this.<>4__this.DoWork().GetAwaiter();
    if (!awaiter.IsCompleted)
    {
    this.<>1__state = num2 = 0;
    this.<>u__1 = awaiter;
    Async.<StartWork>d__2 stateMachine = this;
    this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter, Async.<StartWork>d__2>(ref awaiter, ref stateMachine);
    return;
    }
    }
    else
    {
    awaiter = this.<>u__1;
    this.<>u__1 = new TaskAwaiter();
    this.<>1__state = num2 = -1;
    }
    awaiter.GetResult();
    UnityEngine.Debug.Log((object) ("end.." + (object) Thread.CurrentThread.ManagedThreadId));
    }
    catch (Exception ex)
    {
    this.<>1__state = -2;
    this.<>t__builder.SetException(ex);
    return;
    }
    this.<>1__state = -2;
    this.<>t__builder.SetResult();
    }

    [DebuggerHidden]
    void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
    {
    }
    }

    [CompilerGenerated]
    private sealed class <DoWork>d__3 : IAsyncStateMachine
    {
    public int <>1__state;
    public AsyncTaskMethodBuilder <>t__builder;
    public Async <>4__this;
    private Task <task>5__1;
    private TaskAwaiter <>u__1;

    public <DoWork>d__3()
    {
    base..ctor();
    }

    void IAsyncStateMachine.MoveNext()
    {
    int num1 = this.<>1__state;
    try
    {
    TaskAwaiter awaiter;
    int num2;
    if (num1 != 0)
    {
    UnityEngine.Debug.Log((object) ("task start.." + (object) Thread.CurrentThread.ManagedThreadId));
    this.<task>5__1 = Task.Delay(1000);
    awaiter = this.<task>5__1.GetAwaiter();
    if (!awaiter.IsCompleted)
    {
    this.<>1__state = num2 = 0;
    this.<>u__1 = awaiter;
    Async.<DoWork>d__3 stateMachine = this;
    this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter, Async.<DoWork>d__3>(ref awaiter, ref stateMachine);
    return;
    }
    }
    else
    {
    awaiter = this.<>u__1;
    this.<>u__1 = new TaskAwaiter();
    this.<>1__state = num2 = -1;
    }
    awaiter.GetResult();
    UnityEngine.Debug.Log((object) ("task end.." + (object) Thread.CurrentThread.ManagedThreadId));
    }
    catch (Exception ex)
    {
    this.<>1__state = -2;
    this.<>t__builder.SetException(ex);
    return;
    }
    this.<>1__state = -2;
    this.<>t__builder.SetResult();
    }

    [DebuggerHidden]
    void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
    {
    }
    }
    }
  • 可以看到async关键字修饰的方法都会生成一个实现IAsyncStateMachine接口的类
  • num1表示当前状态,0 = 异步操作未开始 -1 = 异步操作已完成 -2 = 抛出异常
  • AwaitUnsafeOnCompleted方法来挂起异步操作,等待异步操作完成后继续执行

Unity异步详解
https://baifabaiquan.cn/2023/09/05/unity异步详解/
作者
白发败犬
发布于
2023年9月5日
许可协议