Web2py - 运行 在不阻塞应用程序的情况下循环和中断循环

Web2py - Run Loop & Break Loop Without Locking Application

不确定如何解决此类问题(更不用说高效解决了)。

在我的应用程序中,用户应该能够通过 ajax 单击按钮来启动流程。他们还应该能够通过单击停止按钮随时停止该过程。类似于:

def start():
    session.test = True 
    items=["1","2","3","4"]
    while session.test == True:
        for item in items:
           ..do stuff..
        pass 

def stop():
    session.test = False

但是,这不起作用。在这些问题中,当循环启动时应用程序锁定。如何在不锁定应用程序的情况下让多个用户 运行 他们自己的循环(并能够打破它们)?

编辑:

我应该注意到 ..do stuff.. 正在利用 selenium 浏览器。

任何基于事件循环的程序,无论是服务器还是 GUI,都是固有的,你不能在事件处理程序中执行像这样 while 循环这样的长运行ning 事情.如果你尝试一下,你就会得到你所看到的。当程序 运行ning 您的 while 循环时,主事件循环不是 运行ning。所以,start 必须立即 return。

一般有两种解决方法:

  • 多线程。 start 函数启动一个后台线程(或进程)来完成工作,然后是 returns。当然,这意味着必须在循环和程序的其余部分之间共享的任何内容(例如 session.test)都需要通过锁或其他同步对象进行保护。
  • 中断循环,以便稍后安排回调到 运行。该回调仅检查您是否完成,如果没有,运行 是循环的一个步骤,并再次安排自身到 运行。当然,这意味着将您的逻辑由内而外。

这两个都有变体。您可以使用子进程,或 greenlet-style 具有显式让步的用户线程,而不是线程。或者,您可以将回调包装在 promises 或协程中。但这是两个基本的想法。您必须选择一个,然后阅读它。

当涉及长运行 任务时,在 web2py 模型、视图或控制器中执行该任务是个坏主意,因为这会阻塞整个请求线程,直到任务完成。同时,这也迫使服务器为该任务的后续请求打开新线程。如果您有太多活动请求,您的整个网站将变慢或锁定。 (有解决方案,比如用 gevent 猴子修补 web2py,但这是一个高级主题)

在web2py中,最好把一个长的运行任务放在自己的进程(与应用进程分开),让任务与通过数据库申请。 Web2py 对此有多种解决方案,例如 Cron、自制任务队列和调度程序。 See here.

让我们做自制任务队列吧,因为它是最简单的。在此示例中,我们要发送电子邮件以确认图书订单。目前我们发送这样的电子邮件:

def controller():
     #... controller stuff (i.e. buyer purchased a book)
     #... send a confirmation email
     mail.send(
          to = 'buyer@example.com',
          subject = 'Bookstore order',
          message ='Thank you for your order! Your new book will ship within 24 hours.',
     )
     #... 5 seconds later (blocks)
     #... continue

但是,这会在发送电子邮件 (例如 5 秒) 的整个时间内阻止请求,因为 mail.send() 是一个缓慢的过程-运行 任务。这对用户体验不利,因为用户会看到他的浏览器加载额外 5 秒。

让我们改为将电子邮件推送到数据库,并让一个单独的后台进程轮询数据库以获取新电子邮件,然后将其发送出去。例如,在我们的控制器中:

def controller():
     #... controller stuff (i.e. buyer purchased a book)
     #... push confirmation email to db
     db.queue.insert(status='pending',
          email='buyer@example.com',
          subject = 'Bookstore order',
          message ='Thank you for your order! Your new book will ship within 24 hours.',
     )
     #... milliseconds later (no block)
     #... continue

注意这次完成需要几毫秒,用户不会看到浏览器变慢。

在我们的 web2py/private 文件夹中,让我们在 mail_queue.py:

中编写我们的小后台脚本
## in file /bookstore/private/mail_queue.py
import time
while True:
    rows = db(db.queue.status=='pending').select()
    for row in rows:
        if mail.send(to=row.email,
            subject=row.subject,
            message=row.message):
            row.update_record(status='sent')
        else:
            row.update_record(status='failed')
        db.commit()
    time.sleep(60) # poll every minute for new emails

注意它如何像控制器一样工作,因为您可以访问与控制器相同的全局变量。唯一的区别是您必须执行 db.commit() 以将更改保存到数据库。

最后在 shell,让我们启动后台进程。注意,它将与 web2py 应用程序处于单独的进程中,并且不会相互干扰。

python web2py.py -S app -M -R applications/bookstore/private/mail_queue.py