首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用django restframework使用python异步/等待

使用django restframework使用python异步/等待
EN

Stack Overflow用户
提问于 2017-10-18 22:08:07
回答 2查看 7.7K关注 0票数 7

我只是将一个较旧的项目升级到Python3.6,发现有这些很酷的新异步/等待关键字。

我的项目包含一个网络爬虫,这是目前还不是很好的表现,大约需要7分钟才能完成。现在,由于已经有了django restframework来访问django应用程序的数据,所以我认为最好有一个REST端点,在那里我可以通过一个简单的POST请求从远程启动爬虫。

但是,我不希望客户端同步等待爬虫完成。我只想马上给他发送信息,说爬虫已经启动,并在后台启动爬虫。

代码语言:javascript
复制
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from django.conf import settings
from mycrawler import tasks

async def update_all_async(deep_crawl=True, season=settings.CURRENT_SEASON, log_to_db=True):
    await tasks.update_all(deep_crawl, season, log_to_db)


@api_view(['POST', 'GET'])
def start(request):
    """
    Start crawling.
    """
    if request.method == 'POST':
        print("Crawler: start {}".format(request))

        deep = request.data.get('deep', False)
        season = request.data.get('season', settings.CURRENT_SEASON)

        # this should be called async
        update_all_async(season=season, deep_crawl=deep)

        return Response({"Success": {"crawl finished"}}, status=status.HTTP_200_OK)
    else:
        return Response ({"description": "Start the crawler by calling this enpoint via post.", "allowed_parameters": {
            "deep": "boolean",
            "season": "number"
        }}, status.HTTP_200_OK)

我读过一些教程,也读过如何使用循环之类的东西,但我不太明白.在这种情况下,我应该从哪里开始循环?

编辑20/10/2017:

我现在用线程解决了它,因为它确实是一个“火与忘”的任务。但是,我仍然想知道如何使用异步/等待来实现相同的目标。

以下是我目前的解决方案:

代码语言:javascript
复制
import threading


@api_view(['POST', 'GET'])
def start(request):
    ...
    t = threading.Thread(target=tasks.update_all, args=(deep, season))
    t.start()
    ...
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2021-04-09 13:27:21

在Django 3.1+中,在引入异步支持之后,这是可能的。

关于异步运行循环,可以通过使用uvicorn或任何其他ASGI服务器来运行Django,而不是使用gunicorn或其他WSGI服务器。区别在于,当使用ASGI服务器时,已经有一个正在运行的循环,而在使用WSGI时则需要创建一个循环。使用ASGI,您可以直接在async或其视图类的继承函数下定义views.py函数。

假设您使用ASGI,您有多种方法来实现这一点,我将描述几个方法(例如,其他选项可以使用asyncio.Queue ):

  1. start() 异步

通过使start()异步,您可以直接使用现有的运行循环,通过使用asyncio.Task,您可以在现有的运行循环中触发和忘记。如果您想开火,但请记住,您可以创建另一个Task来跟踪这个,即:

代码语言:javascript
复制
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from django.conf import settings
from mycrawler import tasks

import asyncio

async def update_all_async(deep_crawl=True, season=settings.CURRENT_SEASON, log_to_db=True):
    await tasks.update_all(deep_crawl, season, log_to_db)

async def follow_up_task(task: asyncio.Task):
    await asyncio.sleep(5) # Or any other reasonable number, or a finite loop...
    if task.done():
        print('update_all task completed: {}'.format(task.result()))
    else:
        print('task not completed after 5 seconds, aborting')
        task.cancel()


@api_view(['POST', 'GET'])
async def start(request):
    """
    Start crawling.
    """
    if request.method == 'POST':
        print("Crawler: start {}".format(request))

        deep = request.data.get('deep', False)
        season = request.data.get('season', settings.CURRENT_SEASON)

        # Once the task is created, it will begin running in parallel
        loop = asyncio.get_running_loop()
        task = loop.create_task(update_all_async(season=season, deep_crawl=deep))

        # Fire up a task to track previous down
        loop.create_task(follow_up_task(task))

        return Response({"Success": {"crawl finished"}}, status=status.HTTP_200_OK)
    else:
        return Response ({"description": "Start the crawler by calling this enpoint via post.", "allowed_parameters": {
            "deep": "boolean",
            "season": "number"
        }}, status.HTTP_200_OK)
  1. 异步

有时候,您不能仅仅有一个async函数来将请求路由到就像DRF发生的那样 (从今天起)。为此,Django提供了一些有用的适配器功能,但请注意,从同步切换到异步上下文(反之亦然),轻微的表现惩罚大约有1ms。请注意,这次聚集在update_all_sync函数中的运行循环:

代码语言:javascript
复制
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from django.conf import settings
from mycrawler import tasks

import asyncio
from asgiref.sync import async_to_sync

@async_to_sync
async def update_all_async(deep_crawl=True, season=settings.CURRENT_SEASON, log_to_db=True):
    #We can use the running loop here in this use case
    loop = asyncio.get_running_loop()
    task = loop.create_task(tasks.update_all(deep_crawl, season, log_to_db))
    loop.create_task(follow_up_task(task))

async def follow_up_task(task: asyncio.Task):
    await asyncio.sleep(5) # Or any other reasonable number, or a finite loop...
    if task.done():
        print('update_all task completed: {}'.format(task.result()))
    else:
        print('task not completed after 5 seconds, aborting')
        task.cancel()


@api_view(['POST', 'GET'])
def start(request):
    """
    Start crawling.
    """
    if request.method == 'POST':
        print("Crawler: start {}".format(request))

        deep = request.data.get('deep', False)
        season = request.data.get('season', settings.CURRENT_SEASON)

        # Make update all "sync"
        sync_update_all_sync = async_to_sync(update_all_async)
        sync_update_all_sync(season=season, deep_crawl=deep)

        return Response({"Success": {"crawl finished"}}, status=status.HTTP_200_OK)
    else:
        return Response ({"description": "Start the crawler by calling this enpoint via post.", "allowed_parameters": {
            "deep": "boolean",
            "season": "number"
        }}, status.HTTP_200_OK)

在这两种情况下,函数将很快返回200,但从技术上讲,第二个选项要慢一些。

重要的:在使用Django时,通常在这些异步操作中包含DB操作。Django中的DB操作只能是同步的,至少现在是这样,所以您必须在异步上下文中考虑这一点。对于这些情况,sync_to_async()变得非常方便。

票数 6
EN

Stack Overflow用户

发布于 2017-10-19 01:38:04

在我看来,您应该看看芹菜,它是专门为异步任务设计的一个很好的工具。它支持Django,当您不希望用户等待服务器上的长时间操作时,它非常有用。在后台运行的每个任务都接收一个task_id,如果您想要创建另一个服务,给定一个task_id,返回某个特定任务是否成功,或者到目前为止已经完成了多少任务,它可以帮助您创建另一个服务。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/46820009

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档