我可以对Python中的任何函数使用sync_to_async吗?

问题描述 投票:0回答:4

背景: 我正在开发一个使用请求的 Discord 机器人。请求是异步的,所以我使用库asgiref.sync

(我知道我显然不能将此函数用于异步函数。)

我在所有请求和可能需要很长时间处理的事情中实现了 sync_to_async。该函数不会产生任何错误。不过,我不确定这是否真的有什么作用。

python asynchronous
4个回答
4
投票

我可以对 Python 中的任何函数使用sync_to_async吗?

是的,但是...

这是否意味着我可以将任何函数转换为异步函数,以使事情运行得更快、更有凝聚力(...)?

不。这是一个代码示例:

import asyncio
import time

from asgiref.sync import async_to_sync, sync_to_async


def sleep(t0):
    time.sleep(1)
    print(time.time() - t0)


async def async_sleep(t0):
    await asyncio.sleep(1)
    print(time.time() - t0)


sync_to_async_sleep = sync_to_async(sleep)
async_to_sync_sleep = async_to_sync(async_sleep)


async def async_main():
    print('sync')
    t0 = time.time()
    sleep(t0)
    sleep(t0)

    print('\nsync_to_async')
    t0 = time.time()
    await asyncio.gather(
        sync_to_async_sleep(t0),
        sync_to_async_sleep(t0),
    )

    print('\nasync')
    t0 = time.time()
    await asyncio.gather(
        async_sleep(t0),
        async_sleep(t0),
    )


def main():
    print('\nasync_to_sync')
    t0 = time.time()
    async_to_sync_sleep(t0)
    async_to_sync_sleep(t0)


asyncio.run(async_main())
main()

输出:

sync
1.00
2.01

sync_to_async
1.00
2.01

async
1.00
1.00

async_to_sync
1.00
2.01

显然,只有“异步”情况(异步上下文中的真正异步实现)同时运行。


2
投票

异步并不会神奇地让你的程序并行运行。在异步函数中,需要在某些点上函数“放弃控制”,例如等待远程服务器的响应时。

如果您将函数“转换”为异步(1)CPU 限制或(2)不通过在某处调用

await
语句来放弃控制,或(3)两者,则该函数将运行直到完成为止.

换句话说:异步是协作式多任务处理。异步函数必须在某些点“移交”控制权给异步循环,以允许其他函数运行。


2
投票

如果没有看到你的代码,就很难知道你是否获得了任何好处或者你可以在哪些方面做得更好。

但是,你可以使用

sync_to_async
在不同线程上并发执行同步函数,但是你必须通过
thread_sensitive=False
。因此,只需用 sync_to_async 位来修改
aaron 的答案

import asyncio
import time

from asgiref.sync import sync_to_async


@sync_to_async(thread_sensitive=False)
def async_sleep(t0):
    time.sleep(1)
    print(time.time() - t0)


async def async_main():
    t0 = time.time()
    await asyncio.gather(
        async_sleep(t0),
        async_sleep(t0),
    )


asyncio.run(async_main())

输出:

1.0007977485656738
1.0009398460388184

但是要小心,根据 asgiref 自述文件

请注意,运行的线程是非常具体的[...snip...] 默认情况下,出于安全原因,sync_to_async 将在同一线程中运行程序中的所有同步代码您可以使用

@sync_to_async(thread_sensitive=False)
禁用此功能...,但请确保您的代码不依赖于任何绑定到线程的内容(例如数据库连接)。

(强调我的。)

因此,在 @aaron 的示例中,它不起作用的原因是因为两个同步函数在同一线程上相继运行。为了获得并发性,你只需添加

thread_sensitive=False
,现在两个同步函数调用在单独的线程上并行运行(或者至少按照 GIL 允许的并行程度),但现在你有责任确保它们可以处理并行运行并且不会互相踩踏。

您可能还想知道,如果不设置

sync_to_async
thread_sensitive=False
的意义何在。在这个例子中没有,因为同步函数被多次分派,并且希望它们能够同时运行,但事实并非如此。但它确实允许您与异步函数并行运行单个同步函数,如下所示:

import asyncio
import time

from asgiref.sync import sync_to_async


@sync_to_async
def async_sleep(t0):
    time.sleep(1)
    print(time.time() - t0)


async def real_async_sleep(t0):
    await asyncio.sleep(1)
    print(time.time() - t0)


async def async_main():
    t0 = time.time()
    await asyncio.gather(
        async_sleep(t0),
        real_async_sleep(t0),
    )


asyncio.run(async_main())

输出:

1.0015418529510498
1.0017149448394775

0
投票

不要使用 (thread_sensitive=False),只需在 settings.py 中添加 os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true" 即可。

#views.py
from django.http import HttpResponse
import time
from .models import Movies, Theatres
from asgiref.sync import sync_to_async
import asyncio

#helper functions
def get_movies():
    print("getting movies ....")
    time.sleep(8)
    qs = Movies.objects.all()
    print(qs)
    print("all movies fetched")

def get_theatres():
    print("getting theatres ...")
    time.sleep(3)
    qs = Theatres.objects.all()
    print(qs)
    print("all theatres fetched")



async def get_movies_async():
    print("getting movies ....")
    await asyncio.sleep(8)
    qs = Movies.objects.all()
    print(qs)
    print("all movies fetched")

async def get_theatres_async():
    print("getting theatres ...")
    await asyncio.sleep(3)
    qs = Theatres.objects.all()
    print(qs)
    print("all theatres fetched")



#Main Views

def sync_view(request):
    start_time = time.time()
    get_movies()
    get_theatres()
    total = time.time()-start_time
    return HttpResponse(f"time taken {total}")


async def async_view(request):
    start_time = time.time()
    await asyncio.gather(get_movies_async(), get_theatres_async())
    total = time.time()-start_time
    return HttpResponse(f"time taken async {total}")
© www.soinside.com 2019 - 2024. All rights reserved.