Path: csiph.com!fu-berlin.de!uni-berlin.de!not-for-mail From: Ian Kelly Newsgroups: comp.lang.python Subject: Re: Question about asyncio and blocking operations Date: Sat, 23 Jan 2016 08:44:33 -0700 Lines: 99 Message-ID: References: Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 X-Trace: news.uni-berlin.de QeBwrEVtKjfP9pahFCRXxglrhPXIQG2g5chVM1MEts8Q== Return-Path: X-Original-To: python-list@python.org Delivered-To: python-list@mail.python.org X-Spam-Status: OK 0.000 X-Spam-Evidence: '*H*': 1.00; '*S*': 0.00; 'received:209.85.223': 0.03; 'cache': 0.05; 'method.': 0.05; 'subject:Question': 0.05; 'method,': 0.07; 'objects,': 0.07; 'params': 0.07; '__init__': 0.09; 'caveat': 0.09; 'fix.': 0.09; 'lookup': 0.09; 'occurrences': 0.09; 'jan': 0.11; 'def': 0.13; 'subsequent': 0.15; '2016': 0.16; '23,': 0.16; 'async': 0.16; 'coroutines': 0.16; 'dictionary.': 0.16; 'normal,': 0.16; 'parts,': 0.16; 'received:io': 0.16; 'received:psf.io': 0.16; 'stuff)': 0.16; 'wrote:': 0.16; 'app': 0.16; 'library,': 0.18; 'typical': 0.18; 'explicit': 0.22; 'am,': 0.23; '(or': 0.23; 'seems': 0.23; 'replacing': 0.23; 'sat,': 0.23; 'slightly': 0.23; 'split': 0.23; 'this:': 0.23; 'header:In-Reply- To:1': 0.24; 'requests': 0.25; "doesn't": 0.26; 'sense': 0.26; 'skip:m 30': 0.27; 'skip:t 40': 0.27; 'least': 0.27; 'message- id:@mail.gmail.com': 0.27; 'function': 0.28; 'initial': 0.28; 'allowed,': 0.29; 'bad.': 0.29; 'blocking': 0.29; 'such.': 0.29; 'thread,': 0.29; 'objects': 0.29; 'classes': 0.30; 'skip:g 30': 0.30; 'task': 0.30; 'fixed': 0.31; 'anyone': 0.32; 'another': 0.32; 'skip:_ 10': 0.32; 'returned': 0.32; 'run': 0.33; 'class': 0.33; 'common': 0.33; 'similar': 0.33; 'recommended': 0.34; 'structure': 0.34; 'worked': 0.34; 'handle': 0.34; 'skip:d 20': 0.34; 'received:google.com': 0.35; 'could': 0.35; 'programming.': 0.35; 'something': 0.35; 'level': 0.35; 'problem.': 0.35; 'but': 0.36; 'too': 0.36; 'instead': 0.36; 'received:209.85': 0.36; 'depends': 0.36; 'to:addr:python-list': 0.36; 'subject:: ': 0.37; 'two': 0.37; 'method': 0.37; 'turn': 0.37; 'thought': 0.37; 'doing': 0.38; 'itself': 0.38; 'received:209': 0.38; 'end': 0.39; 'data': 0.39; 'does': 0.39; 'to:addr:python.org': 0.40; 'where': 0.40; 'some': 0.40; 'company': 0.60; 'easy': 0.60; 'your': 0.60; 'back': 0.62; 'different': 0.63; "they're": 0.66; 'here': 0.66; 'frank': 0.72; 'await': 0.76; 'sounds': 0.76; 'exercise,': 0.84; 'face,': 0.84; 'to:name:python': 0.84; 'imagine': 0.96 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=mime-version:in-reply-to:references:from:date:message-id:subject:to :content-type; bh=YBW5GO+6lAE+CaHu2EQuFPuXoqSeiDbBBwM6ot57pZw=; b=EizTZxHrOKthzK2i9Vsje0I5zJI83r9ZBfUT+JpB/yfoK9ob/Zb3A0eNOQJNXJdngW QgkGH6j32oNmOHibCp1u9w6NHPcv00bPQF/Q/Uyb0uYtXxk+sazMo/K460TaUdv0iWrz hEpbDKP6QI+z+FfRl9rD6rPhIJLlH6nCbQ+GZzonUEthPY78Q/jkNiDfhPh+ldWWcepK nAhZHmdthjq6SXiI+F/K75VjqBvgeFiu1/Tm/pPVKoeo0Xglpv2Mxf5xlu3g+gHtgxxG ywziZtnl017ceJ9C0bQ8EzLEGJOXMKknLkatTisVfBnti5HN8PjbY76/EDSd6RxDk6RO Q5Ow== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:mime-version:in-reply-to:references:from:date :message-id:subject:to:content-type; bh=YBW5GO+6lAE+CaHu2EQuFPuXoqSeiDbBBwM6ot57pZw=; b=Wqo7HAv+GqV4NWK67nOBWlpZ65K3TCBaihape8r0r8RwwbK496PXc4HVCUpD3+vUmL j2jvGOYZFqOgu4YohFDvem7/WZ0St+MkTIO2DOHiODpf1bgnMKR4dmFgyKGIbSDrZQyT P6Fa5i613ztFx4IfWgxRIcJ0NBjVjChw5ZBkBqfJw7X6NolXVOfGBC7g16wDAtxGfgkS ijoLLLyGpl1Ril/ZodVrsYINWl37u2T7BDGufHGK/rsAcvq6wNIDgFIfgv3XDhMoEoMM vRkrfHVaDvghVvwd82R4w1tl8Ru3p4yTLSzASxfpXMDLVS94wInq1deV+rk3+kIFRmU6 26Fg== X-Gm-Message-State: AG10YOTlDZ2iG/aH+dJ5mtD8smkxXLE5/UuUn/5+yOZK+3cptU0coUlCRTjyWHnqA+03KfjMxjcO010n1JuAAg== X-Received: by 10.107.11.68 with SMTP id v65mr8849574ioi.188.1453563912962; Sat, 23 Jan 2016 07:45:12 -0800 (PST) In-Reply-To: X-BeenThere: python-list@python.org X-Mailman-Version: 2.1.20+ Precedence: list List-Id: General discussion list for the Python programming language List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Xref: csiph.com comp.lang.python:102051 On Sat, Jan 23, 2016 at 7:38 AM, Frank Millman wrote: > Here is the difficulty. The recommended way to handle a blocking operation > is to run it as task in a different thread, using run_in_executor(). This > method is a coroutine. An implication of this is that any method that calls > it must also be a coroutine, so I end up with a chain of coroutines > stretching all the way back to the initial event that triggered it. This seems to be a common misapprehension about asyncio programming. While coroutines are the focus of the library, they're based on futures, and so by working at a slightly lower level you can also handle them as such. So while this would be the typical way to use run_in_executor: async def my_coroutine(stuff): value = await get_event_loop().run_in_executor(None, blocking_function, stuff) result = await do_something_else_with(value) return result This is also a perfectly valid way to use it: def normal_function(stuff): loop = get_event_loop() coro = loop.run_in_executor(None, blocking_function, stuff) task = loop.create_task(coro) task.add_done_callback(do_something_else) return task > I use a cache to store frequently used objects, but I wait for the first > request before I actually retrieve it from the database. This is how it > worked - > > # cache of database objects for each company > class DbObject(dict): > def __missing__(self, company): > db_object = self[company] = get_db_object _from_database() > return db_object > db_objects = DbObjects() > > Any function could ask for db_cache.db_objects[company]. The first time it > would be read from the database, on subsequent requests it would be returned > from the dictionary. > > Now get_db_object_from_database() is a coroutine, so I have to change it to > db_object = self[company] = await get_db_object _from_database() > > But that is not allowed, because __missing__() is not a coroutine. > > I fixed it by replacing the cache with a function - > > # cache of database objects for each company > db_objects = {} > async def get_db_object(company): > if company not in db_objects: > db_object = db_objects[company] = await get_db_object > _from_database() > return db_objects[company] > > Now the calling functions have to call 'await > db_cache.get_db_object(company)' > > Ok, once I had made the change it did not feel so bad. This all sounds pretty reasonable to me. > Now I have another problem. I have some classes which retrieve some data > from the database during their __init__() method. I find that it is not > allowed to call a coroutine from __init__(), and it is not allowed to turn > __init__() into a coroutine. > > I imagine that I will have to split __init__() into two parts, put the > database functionality into a separately-callable method, and then go > through my app to find all occurrences of instantiating the object and > follow it with an explicit call to the new method. > > Again, I can handle that without too much difficulty. But at this stage I do > not know what other problems I am going to face, and how easy they will be > to fix. > > So I thought I would ask here if anyone has been through a similar exercise, > and if what I am going through sounds normal, or if I am doing something > fundamentally wrong. This is where it would make sense to me to use callbacks instead of subroutines. You can structure your __init__ method like this: def __init__(self, params): self.params = params self.db_object_future = get_event_loop().create_task( get_db_object(params)) async def method_depending_on_db_object(): db_object = await self.db_object_future result = do_something_with(db_object) return result The caveat with this is that while __init__ itself doesn't need to be a coroutine, any method that depends on the DB lookup does need to be (or at least needs to return a future).