asgiref icon indicating copy to clipboard operation
asgiref copied to clipboard

Genorators using database_sync_to_async are called on the main thread

Open agronick opened this issue 7 years ago • 3 comments

This is probably true for the other decorators as well. Same issue if you call the decorators directly.

threading.get_ident() is the same as the async code when the function uses yield. If you change it to return a list it has a different ID.

Heres the code

    @sync_to_async
    def function_foo(self):
        print(threading.get_ident(), 'function')
        return 4

    @sync_to_async
    def generator_foo(self):
        print(threading.get_ident(), 'generator')
        yield 4

    async def receive_json(self, content):
        print(threading.get_ident(), 'loop')
        print(await self.function_foo())
        for i in await self.generator_foo():
            print(i)

Heres the output


139945948370688 loop
139945849284352 function
4
139945948370688 generator
4

agronick avatar Apr 16 '18 00:04 agronick

Finally getting back to this (I must have let it slip through last year, sorry).

You're totally right about this - we should see if there's a way to either detect this and raise an error, or intercept it and actually run it properly.

andrewgodwin avatar Jun 24 '19 05:06 andrewgodwin

import threading

class YourClass: def function_foo(self): print(threading.get_ident(), 'function') return 4

async def generator_foo(self):
    print(threading.get_ident(), 'generator')
    yield 4

async def receive_json(self, content):
    print(threading.get_ident(), 'loop')
    print(self.function_foo())  # Call the synchronous function directly
    async for i in self.generator_foo():  # Use 'async for' to iterate over the asynchronous generator
        print(i)

I Guess this will work

baymax-14 avatar Oct 29 '23 05:10 baymax-14

import asyncio
import threading
from asgiref.sync import sync_to_async

@sync_to_async
def function_foo():
    print(threading.get_ident(), 'function')
    return 4

def run_in_thread(func):
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    result = loop.run_until_complete(func())
    loop.close()
    return result

async def generator_foo():
    print(threading.get_ident(), 'generator')
    return await sync_to_async(lambda: 4)()

async def receive_json(content):
    print(threading.get_ident(), 'loop')
    print(await function_foo())

    # Run the generator part in a separate thread
    result = run_in_thread(generator_foo)
    print(result)

Here is what I think could also work. The key addition is the run_in_thread function, which is used to execute the generator part (generator_foo) in a separate thread with its own event loop. This ensures a clear separation between asynchronous and synchronous code execution.

davitacols avatar Dec 23 '23 04:12 davitacols