如何避免AppConfig.ready()方法在Django中运行两次



我想在Django服务器启动时执行一些代码,但我希望它只运行一次。目前,当我启动服务器时,它会执行两次。文件表明这可能会发生,并且:

应该在AppConfig类上设置一个标志,以防止重新运行应该只执行一次的代码。

知道如何做到这一点吗?下面的打印语句仍然执行两次。

from django.apps import AppConfig
import app.mqtt
from apscheduler.schedulers.background import BackgroundScheduler
class MyAppConfig(AppConfig):
    name = 'app'
    verbose_name = "HomeIoT"
    run_already = False
    def ready(self):
        if MyAppConfig.run_already: return
        MyAppConfig.run_already = True
        print("Hello")

使用python manage.py runserver时,Django启动两个进程,一个用于实际的开发服务器,另一个用于在代码更改时重新加载应用程序。

您可以在没有重新加载选项的情况下启动服务器,并且您将只看到一个进程在运行:

python manage.py runserver --noreload

另请参阅在Django中运行两次的ready()方法。

如果您不想使用--noreload,您可以:

替换应用程序的__init__.py中用于指定配置的行:

default_app_config = 'mydjangoapp.apps.MydjangoappConfig'

通过这个:

import os
if os.environ.get('RUN_MAIN', None) != 'true':
    default_app_config = 'mydjangoapp.apps.MydjangoappConfig'

或者,在AppConfig ready方法中检查RUN_MAIN环境变量:

import os
from django.apps import AppConfig
class MyAppConfig(AppConfig):
    name = 'app'
    def ready(self):
        if os.environ.get('RUN_MAIN'):
            print('Hello')

我发现在没有使用python manage.py runserver中的--noreload标志的情况下,这对我很有效。

检查ready()方法中的环境变量。env变量在应用程序结束后不会持久存在,但如果服务器检测到代码更改并自动重新加载后,env变量会持久存在。

# File located in mysite/apps.py
from django.apps import AppConfig
import os
class CommandLineRunner(AppConfig):
    name = 'mysite'
    def ready(self):
        run_once = os.environ.get('CMDLINERUNNER_RUN_ONCE') 
        if run_once is not None:
            return
        os.environ['CMDLINERUNNER_RUN_ONCE'] = 'True' 
        # The code you want to run ONCE here
  

您需要实现锁定。这不是一个简单的问题,在处理进程和线程时,解决方案也不会很自然。请注意,锁定问题有很多答案,一些更简单的方法:

文件锁:确保Linux中的应用程序只有一个实例(请注意,默认情况下线程共享文件锁,因此需要扩展此答案以考虑线程)。

还有一个答案,它使用了一个名为tendo的Python包,该包封装了一个文件锁实现:https://stackoverflow.com/a/1265445/181907

Django本身在django.core.files.locks中提供了一个抽象的可移植文件锁定实用程序。

正如Roberto所提到的,如果您想使用默认的auto_reload功能,那么在通过runserver命令运行服务器时,您需要实现锁定。

Django通过线程实现了它的auto_reload,因此在两个独立的线程中导入AppConfig,即运行服务器的主"command/watch"线程和"reload"线程。将打印语句添加到模块中,您将看到它的实际操作。"main"线程加载AppConfig文件作为其BaseCommand执行的一部分,然后"reload"线程在启动服务器期间再次加载这些文件。

如果您的代码不能同时在这两个线程中运行,那么您的选择会受到一定的限制。你可以实现一个线程锁,这样'reload'线程就不会运行ready();你可以转移到生产环境来运行你的服务器(例如,Gunicorn的设置非常快,甚至是测试);或者,您可以用另一种方式调用方法,而不是使用ready()。

我建议转移到一个合适的环境中,但最好的选择实际上取决于您调用的方法应该做什么。

发现AppConfig被激发了两次,并导致调度程序使用此配置启动了两次。相反,在urls.py中实例化调度程序,如下所示:

urlpatterns = [
    path('api/v1/', include(router.urls)),
    path('api/v1/login/', CustomTokenObtainPairView.as_view(), name='token_obtain_pair'),
    path('api/v1/login/refresh/', jwt_views.TokenRefreshView.as_view(), name='token_refresh'),
    path('api/v1/', include('rest_registration.api.urls'))
]
scheduler = BackgroundScheduler()
scheduler.add_job(task.run, trigger='cron', hour=settings.TASK_RUNNER_HOURS, minute=settings.TASK_RUNNER_MINUTES, max_instances=1)
scheduler.start()

最新更新