所以有一种非常骇人听闻的方式来完成你可能想要的......
在django.apps.registry 内部是单例apps,Django 使用它来填充应用程序。请参阅django.__init__.py 中的setup。
apps.populate 的工作方式是它使用不可重入(基于线程)锁定机制,只允许 apps.populate 以幂等、线程安全的方式发生。
Apps 类的精简源代码是单例 apps 的实例化来源:
class Apps(object):
def __init__(self, installed_apps=()):
# Lock for thread-safe population.
self._lock = threading.Lock()
def populate(self, installed_apps=None):
if self.ready:
return
with self._lock:
if self.ready:
return
for app_config in self.get_app_configs():
app_config.ready()
self.ready = True
有了这些知识,您可以创建一些在某些条件下等待的threading.Thread。这些消费者线程将利用threading.Condition 发送跨线程信号(这将强制执行您的排序问题)。这是一个模拟的例子,说明它是如何工作的:
import threading
from django.apps import apps, AppConfig
# here we are using the "apps._lock" to synchronize our threads, which
# is the dirty little trick that makes this work
foo_ready = threading.Condition(apps._lock)
class FooAppConfig(AppConfig):
name = "foo"
def ready(self):
t = threading.Thread(name='Foo.ready', target=self._ready_foo, args=(foo_ready,))
t.daemon = True
t.start()
def _ready_foo(self, foo_ready):
with foo_ready:
# setup foo
foo_ready.notifyAll() # let everyone else waiting continue
class BarAppConfig(AppConfig):
name = "bar"
def ready(self):
t = threading.Thread(name='Bar.ready', target=self._ready_bar, args=(foo_ready,))
t.daemon = True
t.start()
def _ready_bar(self, foo_ready):
with foo_ready:
foo_ready.wait() # wait until foo is ready
# setup bar
同样,这只允许您控制来自个人AppConfig 的ready 呼叫的流程。这不控制加载的订单模型等。
但是,如果您的第一个断言是正确的,那么您有一个 app.ready 实现,它依赖于另一个应用程序首先准备好,这应该可以解决问题。
推理:
为什么是条件? 使用threading.Condition 而不是threading.Event 的原因有两个。首先,条件被包裹在一个锁定层中。这意味着如果需要(访问共享资源等),您将继续在受控情况下操作。其次,由于这种严格的控制级别,留在threading.Condition 的上下文中将允许您以某种理想的顺序链接配置。您可以使用以下 sn-p 查看如何完成此操作:
lock = threading.Lock()
foo_ready = threading.Condition(lock)
bar_ready = threading.Condition(lock)
baz_ready = threading.Condition(lock)
为什么是恶魔线程? 这样做的原因是,如果您的 Django 应用程序在获取和释放apps.populate 中的锁之间的某个时间死掉,后台线程将继续旋转等待锁释放。将它们设置为 daemon-mode 将允许进程干净地退出,而无需 .join 那些线程。