Timer at ready() blocks makemigrations, but not runserver

Hi, I was trying to create an app which contains a function run periodically (eg:run some tasks for every 10 minutes such as removing expired tokens from a global variable, but now just print a message for every 3 seconds for simpler codes), so I define the function at SomeConfig.ready() as the following:

class SomeConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'Some'

    def ready(self):
        def bg_task():
            print('bg_task')
            threading.Timer(3,bg_task).start()
        threading.Timer(3,bg_task).start()
        print('ready called')

The app looks normal when run python3 manage.py runserver (browser can receive response when calling API defined at views.py of the app).

However, when I run python3 manage.py makemigrations, the command window seems blocked by bg_task that the migration task seems would not finish:

.
.
.
No changes detected
bg_task
bg_task
bg_task

The result above is, I need to manually delete the code of bg_task temporally when I need makemigration. Is it normal, or just I have some wrong settings?

I also tried:

def bg_task():
    print('bg_task')
    time.sleep(3)
    threading.Thread(target=bg_task).start()
bg_task()
print('ready called')

but it seems have the same effect (block makemigrations but not runserver). And tried:

async def bg_task():
    print('bg_task')
        await asyncio.sleep(3)
        asyncio.run(bg_task())
    asyncio.run(bg_task())
print('ready called')

but this time it seems block both makemigrations and runserver.

While I believe my project would run makemigrations rarely and can be solved by deleting the periodic function temporally when running makemigrations, is there any way to define periodic functions that make both python3 manage.py makemigrations and python3 manage.py runserver run normally, as if no periodic functions added before? Or I need to create an app to hold the periodic functions that never runs makemigrations?

The recommended method to do background tasks is to use the tasks framework, which would have as separate worker process.

Just created a tasks.py at the project level:

from django.tasks import task
@task
def test_task():
    print('*** test task ***')

test_task.enqueue()

However, it seems never be triggered automatically, instead I need to put the function into my AppConfig to trigger:

class MyAppConfig(AppConfig):
    .
    .
    .

    def ready(self):
        .
        .
        .

@task
def run_second():
    print("*** test")

run_second.enqueue()

is the location of tasks.py wrong? Or I misunderstood the doc that tasks.py is supposed to be triggered by some actions (eg: at ready() or functions in views) instead of found and run automatically by Django?

Any task would need to be triggered somewhere, no task will run automatically. Where you enqueue it will depend on what the task does and how it should be triggered