1

我有一个我用 Python 编写的程序,它使用 Spotipy 库调用 Spotify API 来获取用户当前正在播放的歌曲和相关歌曲的节奏。然后它使用该信息、串行连接和 Arduino 来运行一些灯。

我遇到的问题是我希望程序定期检查 API,看看歌曲是否发生了变化,但是每个 API 调用都会占用我的网络大约 0.15 秒,所以在没有太多调用之后它会搞砸灯的时间。

这是调用速度 API 的函数和一个示例 while 循环。如果你想查看完整的项目和完整的代码,这里是 github 的链接 - https://github.com/eboyce452/lightshow.git

def check_bpm():

    global seconds_per_beat
    global current_track

    current_track = spotify.current_user_playing_track()
    if current_track is None:
        seconds_per_beat = 0.5
    else:
        current_track = json_normalize(current_track)
        current_track = str(current_track['item.id'].iloc[0])
        features = json_normalize(spotify.audio_features(current_track))
        tempo = float(features['tempo'].iloc[0])
        seconds_per_beat = 60/tempo

while True:

    check_bpm()
    pin2.write(1)
    time.sleep(seconds_per_beat)
    pin2.write(0)
    time.sleep(seconds_per_beat)

因此,在一个完美的世界中,我正在寻找一种让 check_bpm() 函数在后台运行的方法,以便灯光可以保持在节拍状态,然后当歌曲发生变化时,让循环中断(使用像 continue 或其他东西)和 seconds_per_beat 的变量被更新。

我真的不知道这是否可能,所以请随意权衡。但我最好奇的是实现一种常见的并发形式,这样当 check_bpm() 函数正在等待 API 调用完成时,它会继续执行 while 循环的其余部分,这样灯就不会熄灭的同步。我已经阅读了很多关于 asyncio 的内容,但是我对它非常陌生,因此可以提供任何帮助,我们将不胜感激。

太感谢了!随时查看该项目的 github,并留下您想要的任何评论或批评。

4

1 回答 1

1

是的 - 因为你有一个表示主循环的“while True”,并且变化的变量只写在一侧,而在另一侧读取,这可以移植到轻松使用 asyncio 或线程。

在多线程中,您将让 pin-sending 函数和 api-reading 函数在单独的线程中连续工作 - 就您而言它们并行运行 - 并且普通变量在 Python 中是线程安全的,仅此而已是它。

您可以在单独的线程中启动 API 读取:

from time import sleep
from threading import Thread
...


def check_bpm():

    global seconds_per_beat
    global current_track

    while True:
        current_track = spotify.current_user_playing_track()
        if current_track is None:
            seconds_per_beat = 0.5
        else:
            current_track = json_normalize(current_track)
            current_track = str(current_track['item.id'].iloc[0])
            features = json_normalize(spotify.audio_features(current_track))
            tempo = float(features['tempo'].iloc[0])
            seconds_per_beat = 60/tempo

        # (if you don't care making another api call right away, just leave
        # here with no pause. Otherwise, insert a reasonable time.sleep here 
        # (inside the while loop))

def main():
    api_thread = Thread(target=check_bpm)
    api_thread.start()
    while True:

        pin2.write(1)
        time.sleep(seconds_per_beat)
        pin2.write(0)
        time.sleep(seconds_per_beat)

main()

另一种方法是使用 asyncio - 它会采用不同的语法 - 但归根结底,您必须将 API 调用委托给另一个线程,除非spotify您使用的库也支持 asyncio。
即使是他们,在“感知代码优雅”方面的“收获”也比任何真实的东西都要多 - 如果您在具有不支持多线程的精简 OS/CPU 设置的设备 PC 中运行此代码,它可能会起作用.

它看起来像这样:

from time import sleep
import asyncio

...


async def check_bpm():

    global seconds_per_beat
    global current_track

    loop = asyncio.get_event_loop()
    while True:
        current_track = await loop.run_in_executor(spotify.current_user_playing_track)
        if current_track is None:
            seconds_per_beat = 0.5
        else:
            current_track = json_normalize(current_track)
            current_track = str(current_track['item.id'].iloc[0])
            features = json_normalize(spotify.audio_features(current_track))
            tempo = float(features['tempo'].iloc[0])
            seconds_per_beat = 60/tempo
        # here we better leave some spacing for the other
        # functions to run, besides the 0.15s duration
        # of the API call
        await asyncio.sleep(1)

async def manage_pins():
    while True:
        pin2.write(1)
        await asyncio.sleep(seconds_per_beat)
        pin2.write(0)
        await asyncio.sleep(seconds_per_beat)


def main():
    loop = asyncio.get_event_loop()
    loop.run_until_complete(asyncio.gather(manage_pins(), check_bpm()))

main()

因此,在这里,Python 代码被重写,以便它自愿暂停执行以等待“其他事情发生”——这发生在“等待”语句上。
与带有线程的代码不同,全局变量只会在引脚控制等待“asyncio.sleep”时更改 - 而在线程代码中,变量更改可以随时发生。

而且由于在 spotify api 上阻塞的调用不是异步的(它是一个普通函数,不是用创建的 async def),我们使用loop.run_in_executor命令调用它 - 这将为我们创建线程和管理安排。此处的不同之处在于,在等待 api 调用时,主要代码 pin 代码可以自由运行。

于 2020-05-15T13:45:08.553 回答