【发布时间】:2019-08-01 16:01:41
【问题描述】:
在我的简单 web 应用程序中,我有一个名为 Document 的模型。创建文档时,它是空的。然后用户可以请求生成它,这意味着它的内容是用数据填充的。由于这个生成步骤可能需要一些时间,所以它是一个异步请求:服务器启动一个线程来生成文档,用户得到一个快速响应说生成过程开始,一段时间后生成结束,数据库更新了。
这是描述模型的代码:
import time
from threading import Thread
from django.db import models
STATE_EMPTY = 0
STATE_GENERATING = 1
STATE_READY = 2
class Document(models.Model):
text = models.TextField(blank=True, null=True)
state = models.IntegerField(default=STATE_EMPTY, choices=(
(STATE_EMPTY, 'empty'),
(STATE_GENERATING, 'generating'),
(STATE_READY, 'ready'),
))
def generate(self):
def generator():
time.sleep(5)
self.state = STATUS_READY
self.text = 'This is the content of the document'
self.state = STATE_GENERATING
self.save()
t = Thread(target=generator, name='GeneratorThread')
t.start()
如您所见,generate 函数会更改状态、保存文档并生成线程。线程工作了一段时间(嗯,......睡了一段时间),然后改变状态和内容。
这是对应的测试:
def test_document_can_be_generated_asynchronously(self):
doc = Document()
doc.save()
self.assertEqual(STATE_EMPTY, doc.state)
doc.generate()
self.assertEqual(STATE_GENERATING, doc.state)
time.sleep(8)
self.assertEqual(STATE_READY, doc.state)
self.assertEqual('This is the content of the document', doc.text)
此测试通过。文档对象正确地经历了所有预期的变化。
不幸的是,代码是错误的:更改文档内容后,它永远不会保存,因此更改不是持久的。这可以通过在测试中添加以下行来验证:
self.assertEqual(STATE_READY, Document.objects.first().state)
此断言失败:
self.assertEqual(STATE_READY, Document.objects.first().state)
AssertionError: 2 != 1
解决方案很简单:只需在generator 函数的末尾添加self.save()。但这会导致不同类型的问题:
Destroying test database for alias 'default'...
Traceback (most recent call last):
File ".../virtualenvs/DjangoThreadTest-elBGAiyX/lib/python3.7/site-packages/django/db/backends/utils.py", line 82, in _execute
return self.cursor.execute(sql)
psycopg2.errors.ObjectInUse: database "test_postgres" is being accessed by other users
DETAIL: There is 1 other session using the database.
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
...
File ".../virtualenvs/DjangoThreadTest-elBGAiyX/lib/python3.7/site-packages/django/db/backends/utils.py", line 82, in _execute
return self.cursor.execute(sql)
django.db.utils.OperationalError: database "test_postgres" is being accessed by other users
DETAIL: There is 1 other session using the database.
问题似乎与放置在不同线程中的save() 有关。使用的引擎似乎不会影响结果:我在使用 postgresql(如图所示)和 sqlite 时获得几乎相同的错误消息(在这种情况下,错误类似于“数据库表已锁定”)。
一些类似的问题获得了诸如“只需使用 Celery 来管理繁重的处理任务”之类的回复。我宁愿了解我做错了什么以及如何使用 Django 工具来解决它。其实没有繁重的处理,也不需要扩展到大用户(webapp当时是一个用户使用)
【问题讨论】:
标签: python django multithreading