什么时候是 asyncio.需要 Lock() 吗?

When is asyncio.Lock() needed?

提问人:dataengineer22 提问时间:10/28/2023 更新时间:10/28/2023 访问量:41

问:

这是我到目前为止拥有的代码,它使用黯淡的软件包连接到多个蓝牙设备并从中获取数据/通知。这些设备是秤,如果上面没有重物,它们会在一段时间后自动关闭。在给它们施加重量后,它们会打开并开始通知。该代码会持续扫描已打开的设备,以记录其中的数据。关闭后,将调用 disconnect_callback() 函数。

我有一个名为 connected_devices 的全局集,用于跟踪打开的设备。连接设备后,其 MAC 地址将被添加到设置中。当设备断开连接时,其 Mac 地址将从设置中删除(在 disconnect_callback() 函数中)。

我有锁定代码,这些代码目前已被注释掉,以便同步从connected_device集中添加/删除MAC地址,但我不确定是否有必要,因为一切都在scan_and_connect()协程中运行。

import asyncio
import functools
from bleak import BleakClient, BleakScanner

#lock = asyncio.Lock()  
connected_devices = set() 
notify_uuid = "00002A37-0000-1000-8000-00805F9B34FB"

def callback(client, characteristic, data):
    print(client.address, characteristic, data)

def disconnected_callback(client):
    #with lock: 
    connected_devices.remove(client.address)
    print("disconnect from", device.address)

def match_device(device, adv_data):
    #with lock:
    return adv_data.local_name.startswith('BLE') and device.address not in connected_devices

async def scan_and_connect():
    while True:
        device = await BleakScanner.find_device_by_filter(match_device)

        if device is None:
            continue

        client = BleakClient(device, disconnected_callback=disconnected_callback)
        try:
            await client.connect()
            print("connected to", device.address)
            await client.start_notify(functools.partial(callback, client))
            #with lock:
            connected_devices.add(device.address)

        except BleakError:
            # if failed to connect, this is a no-op, if failed to start notifications, it will disconnect
            await client.disconnect()
     

if __name__ == "__main__":
    asyncio.run(scan_and_connect())

有人可以建议在这种情况下是否需要锁吗? 谢谢

python 异步 async-await python-asyncio 协程

评论

0赞 Tim Roberts 10/28/2023
这里没有机会让国家不一致。在任何给定点,设备都可能处于进出状态,但没有设备处于半进半出的位置。因此,不需要锁。

答:

1赞 tdelaney 10/28/2023 #1

从文档 BleakClient 类

disconnected_callback – 将在事件中安排的回调 在客户端断开连接时循环。

Asyncio 保证在等待的调用之间不会运行使用相同事件循环的其他协程。因此,您不必担心您的设置操作会中断,也没有理由锁定。

如果这个集合在事件循环之外被改变,那就是另一回事了。但是,除非使用该集合的所有东西都使用相同的锁,否则锁仍然没有用。

这段代码的风险在于,它假设在暗淡的回调和被跟踪的地址之间始终存在一对一的关系。为了以防万一,您确实应该包装一个异常处理程序。connected_devices.remove(client.address)

asyncio.Lock当您需要在另一个协程可以执行并更改该状态的调用中保持稳定状态时,需要。await

评论

0赞 jsbueno 10/28/2023
另外:即使在多线程代码中,也不需要在 Python 中保护向集合、字典或列表添加数据;这些操作是原子的。
0赞 tdelaney 10/28/2023
@jsbueno - true,但在加载和调用之间有一个窗口,另一个线程中的代码可以在该窗口运行。根据情况,可能需要锁。client.addressremove
0赞 dataengineer22 10/29/2023
当 try 语句中的 connect() 或 start_notify() 失败时,可能会发生您提到的有风险的事情,因此该地址从未添加到集合中,因此 disconnect() 将尝试从集合中删除不存在的地址,对吗?我想不出在其他情况下,黯淡的回调和被跟踪的地址之间不会有一对一的关系。谢谢
0赞 tdelaney 10/30/2023
@dataengineer22 - 这似乎是合理的。但是,在执行之前添加,因为通知可能会在该调用返回之前发生。await client.start_notify