Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.python > #102042

Question about asyncio and blocking operations

Path csiph.com!fu-berlin.de!uni-berlin.de!not-for-mail
From "Frank Millman" <frank@chagford.com>
Newsgroups comp.lang.python
Subject Question about asyncio and blocking operations
Date Sat, 23 Jan 2016 16:38:08 +0200
Lines 85
Message-ID <mailman.178.1453559914.15297.python-list@python.org> (permalink)
Mime-Version 1.0
Content-Type text/plain; format=flowed; charset="iso-8859-1"; reply-type=original
Content-Transfer-Encoding 7bit
X-Trace news.uni-berlin.de GVx92FN7c6N7abikYu7Y0gghdGmfT4A5NWcbHuNDeRwA==
Return-Path <python-python-list@m.gmane.org>
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; 'cache': 0.05; 'ignored': 0.05; 'method.': 0.05; 'subject:Question': 0.05; 'front-end': 0.07; 'method,': 0.07; 'objects,': 0.07; 'back-end': 0.09; 'fix.': 0.09; 'occurrences': 0.09; 'received:80.91': 0.09; 'received:80.91.229': 0.09; 'received:gmane.org': 0.09; 'received:list': 0.09; 'tackle': 0.09; 'def': 0.13; 'subsequent': 0.15; 'async': 0.16; 'bumping': 0.16; 'coroutines': 0.16; 'dictionary.': 0.16; 'expected,': 0.16; 'normal,': 0.16; 'operation.': 0.16; 'parts,': 0.16; 'received:80.91.229.3': 0.16; 'received:io': 0.16; 'received:plane.gmane.org': 0.16; 'received:psf.io': 0.16; 'stage.': 0.16; 'app': 0.16; 'typical': 0.18; 'input': 0.18; 'together.': 0.20; 'aspect': 0.22; 'converted': 0.22; 'explicit': 0.22; 'replacing': 0.23; 'split': 0.23; 'requests': 0.25; 'header:X-Complaints-To:1': 0.26; 'function': 0.28; 'developing': 0.28; 'initial': 0.28; 'allowed,': 0.29; 'bad.': 0.29; 'blocking': 0.29; 'necessary,': 0.29; 'thread,': 0.29; 'ago': 0.29; 'objects': 0.29; 'classes': 0.30; 'system,': 0.30; 'that.': 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; 'similar': 0.33; 'recommended': 0.34; 'worked': 0.34; 'handle': 0.34; 'skip:d 20': 0.34; 'could': 0.35; 'programming.': 0.35; 'something': 0.35; 'problem.': 0.35; 'but': 0.36; 'too': 0.36; 'there': 0.36; 'to:addr:python-list': 0.36; 'two': 0.37; 'method': 0.37; 'turn': 0.37; 'thanks': 0.37; 'received:org': 0.37; 'thought': 0.37; 'doing': 0.38; 'feedback': 0.38; 'end': 0.39; 'why': 0.39; 'data': 0.39; 'does': 0.39; 'application': 0.39; 'to:addr:python.org': 0.40; 'some': 0.40; 'company': 0.60; 'easy': 0.60; 'clients': 0.61; 'back': 0.62; 'more': 0.63; 'different': 0.63; 'here': 0.66; 'frank': 0.72; 'await': 0.76; 'sounds': 0.76; 'exercise,': 0.84; 'face,': 0.84; 'flaw': 0.84; 'surprisingly': 0.84; 'approach.': 0.91; 'imagine': 0.96
X-Injected-Via-Gmane http://gmane.org/
X-Gmane-NNTP-Posting-Host 197.89.197.209
X-MSMail-Priority Normal
Importance Normal
X-Newsreader Microsoft Windows Live Mail 15.4.3502.922
X-MimeOLE Produced By Microsoft MimeOLE V15.4.3502.922
X-BeenThere python-list@python.org
X-Mailman-Version 2.1.20+
Precedence list
List-Id General discussion list for the Python programming language <python-list.python.org>
List-Unsubscribe <https://mail.python.org/mailman/options/python-list>, <mailto:python-list-request@python.org?subject=unsubscribe>
List-Archive <http://mail.python.org/pipermail/python-list/>
List-Post <mailto:python-list@python.org>
List-Help <mailto:python-list-request@python.org?subject=help>
List-Subscribe <https://mail.python.org/mailman/listinfo/python-list>, <mailto:python-list-request@python.org?subject=subscribe>
Xref csiph.com comp.lang.python:102042

Show key headers only | View raw


Hi all

I am developing a typical accounting/business application which involves a 
front-end allowing clients to access the system, a back-end connecting to a 
database, and a middle layer that glues it all together.

Some time ago I converted the front-end from a multi-threaded approach to an 
asyncio approach. It was surprisingly easy, and did not require me to delve 
into asyncio too deeply.

There was one aspect that I deliberately ignored at that stage. I did not 
change the database access to an asyncio approach, so all reading 
from/writing to the database involved a blocking operation. I am now ready 
to tackle that.

I find I am bumping my head more that I expected, so I thought I would try 
to get some feedback here to see if I have some flaw in my approach, or if 
it is just in the nature of writing an asynchronous-style application.

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. I can 
understand why this is necessary, but it does lead to some awkward 
programming.

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.

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.

Thanks for any input

Frank Millman

Back to comp.lang.python | Previous | Next | Find similar | Unroll thread


Thread

Question about asyncio and blocking operations "Frank Millman" <frank@chagford.com> - 2016-01-23 16:38 +0200

csiph-web