Asynchronous programming

Concept and Principle

  • 异步编程与多线程编程

    • 异步编程一般在单线程(或线程池)上实现并发执行,不涉及线程切换,减小了维护多线程的开销。而多线程编程在多核处理器上做并行执行,需要考虑线程间同步以及线程切换等问题
    • 异步编程适合于I/O密集型操作,而多线程编程适合于计算密集型工作。这是因为在I/O过程中线程会被阻塞但依然要维护其占用的内存资源,并且还有进行线程切换开销;而异步编程则避免了这些问题,将I/O操作封装为异步函数,cpu在执行到I/O操作时向DMA发送指令后直接执行其他代码,当I/O操作结束后执行回调
  • async/await 结构

    • async与await被许多语言都设置为了异步编程的语法糖,有多种实现(如python中的coroutine、c#中的Task、js中的promise)。
    • async/await 结构可分成三部分:
      1. 调用方法:该方法调用异步方法,在异步方法执行其任务的时候继续执行该方法下其他代码
      2. 异步方法:在执行完成前立即返回调用方法,在调用方法继续执行的过程中完成任务
      3. await 表达式:用于异步方法内部,指出需要异步执行的任务(在await之前的代码还都是同步执行)。一个异步方法可以包含多个 await 表达式,当异步方法中不包含await表达式时,将会同步执行(顺序执行)异步方法
  • async/await执行过程

    1. 调用方法执行到由async修饰的异步方法,进入该方法先同步执行
    2. 顺序执行到await修饰的语句,调用该语句后立即返回原调用方法
    3. 在原调用方法中继续执行后面的代码,同时异步方法也在执行await的语句
  • 注意

    • 调用方法和异步方法可能是并行的,也可能是并发的,这方面不需要程序员考虑,只需要知道异步方法的执行不影响调用方法的执行
    • 异步方法正常的返回值并不是方法内指明的返回值(python中返回coroutine、c#中返回Task、js中返回promise),但用await修饰会直接返回异步方法内指明的返回值
    • 如果异步方法还未执行完,而在调用方法中就要使用异步方法的result,则会死等到异步方法执行完毕
    • 异步方法一般回调联合使用

Implementation

  • c#中实现的异步编程基于Task,底层是线程池

    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

    App app = new App();
    app.Run();

    class NeuralNetwork
    {
    public double Acc { get; set; }
    public double Loss { get; set; }

    private string _name;

    public NeuralNetwork(string name)
    {
    _name = name;
    }

    //训练结束后调用回调函数
    //由于是异步方法,其不会阻塞界面线程
    public async void TrainAsync(Action<double, double> action)
    {
    Console.WriteLine("我之前还是同步执行");
    await Task.Delay(3000);
    Acc = 0.9f;
    Loss = 0.00466f;
    action(Acc, Loss);
    }

    //虽然也是异步方法,但方法完成后不会通知调用方法,在此场景下还是低效
    //在不需要回调的情境下可以使用
    public async Task<Tuple<double, double>> TrainAsync()
    {
    Console.WriteLine("我之前还是同步执行");

    await Task.Delay(3000);
    Acc = 0.9f;
    Loss = 0.00466f;

    return Tuple.Create(Acc, Loss);
    }
    }

    class App
    {
    private NeuralNetwork _network = new NeuralNetwork("Cnn");
    public void RunWithCallBack()
    {
    for (int i = 0; i < 30; i++)
    {
    Thread.Sleep(500);
    Console.WriteLine("running...");

    //模拟训练模型,需要长时间操作
    if (i == 3)
    //进入到函数中,执行到调用await后立即返回到RunWithCallBack继续执行
    _network.TrainAsync(this.Show);
    }
    }

    public void Run()
    {
    Task<Tuple<double, double>> t = null;
    for (int i = 0; i < 30; i++)
    {

    Thread.Sleep(500);
    Console.WriteLine("running...");


    //模拟训练模型,需要长时间操作
    if (i == 3)
    {
    //如果在这里直接输出,则依然会产生阻塞的效果
    //Console.WriteLine(_network.TrainAsync().Result);

    //使用await,await返回的直接就是_network.TrainAsync().Result
    t = _network.TrainAsync();

    }

    //在第十次刷新时检查是否训练完成,没完成继续等
    if (i == 10)
    Console.WriteLine(t.Result);

    }


    }

    //界面展示精度与损失
    public void Show(double acc, double loss)
    {
    Console.WriteLine($"acc:{acc},loss:{loss}");
    }
    }
  • python实现的异步编程使用了协程概念,与JS一样底层都是单线程

    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
    #asyncio包帮助实现异步编程
    #将所有异步和同步函数加入一个事件循环,在这个循环内按异步定义为每个函数分配时间片
    import asyncio

    async def RunNN(f):
    print("====")
    await asyncio.sleep(2)

    loss=0.0045
    acc=0.97
    f(loss,acc)


    async def app():
    while(True):
    await asyncio.sleep(0.5)
    print("running...")


    asyncio.gather(app(), RunNN(lambda x, y: print(f"loss:{x},acc{y}")))

    async def main():
    await asyncio.gather(app(), RunNN(lambda x, y: print(f"loss:{x},acc{y}")))

    asyncio.run(main())