Asynchronous programming
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 结构可分成三部分:
- 调用方法:该方法调用异步方法,在异步方法执行其任务的时候继续执行该方法下其他代码
- 异步方法:在执行完成前立即返回调用方法,在调用方法继续执行的过程中完成任务
- await 表达式:用于异步方法内部,指出需要异步执行的任务(在await之前的代码还都是同步执行)。一个异步方法可以包含多个 await 表达式,当异步方法中不包含await表达式时,将会同步执行(顺序执行)异步方法
async/await执行过程
- 调用方法执行到由async修饰的异步方法,进入该方法先同步执行
- 顺序执行到await修饰的语句,调用该语句后立即返回原调用方法
- 在原调用方法中继续执行后面的代码,同时异步方法也在执行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())
Comment