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
不确定如何解决此类问题(更不用说高效解决了)。
在我的应用程序中,用户应该能够通过 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