асинхронный вызов с торнадо и pyodbc - PullRequest
2 голосов
/ 03 марта 2011

Я хочу реализовать веб-сервис на основе торнадо, который может предоставлять пользователям сервис запросов к базе данных.Я использовал модуль pyodbc для подключения к базе данных и выполнения запроса.На практике я обнаружил, что печать результата запроса займет много времени.То есть, если бы я использовал следующий код для печати результата запроса

while 1:
    data = cursor.fetchone()
    if not data: break
    self.write(data + '\n')
    self.flush()

, а команда sql была бы похожа на

select * from <a large dummy table>

, торнадо не напечатало бы результат запроса, покацикл окончен.И это занимает много времени.

Я хочу использовать неблокирующую асинхронную функцию торнадо, чтобы другие пользователи также могли использовать веб-сервис, даже если цикл печати запроса запроса текущего пользователя еще не завершен.пока.

Поэтому я пишу что-то вроде:

@tornado.web.asynchronous
def get(self):
    try:
        cnxn = pyodbc.connect(self.server, self.driver, self.table, self.uid, self.pwd)
    except Exception, e:
        print e
        return

    try:
        self.cur = cnxn.execute(self.sql)
    except Exception, e:
        print e
        return

    self.wait_for_query(callback=self.async_callback(self.on_finish))

def wait_for_query(self, callback):
    while 1:
       data = self.cur.fetchone()
       if not data: break
       self.write(data)
       self.flush()
    callback()

def on_finish(self):
    self.finish()

Я прочитал этот пост: Асинхронный запрос COMET с Tornado и Prototype и знал, что мое решение не сработает.Но я, конечно, не могу использовать add_timeout, потому что у меня нет возможности выяснить, как долго продлится итерация.Так как же мне пройти через это, чтобы достичь своей цели?

1 Ответ

0 голосов
/ 06 ноября 2011

Чтобы позволить однопоточному серверу Tornado быть асинхронным в таком запросе, необходимо вернуть управление в цикл ввода-вывода. Попробуйте это:

class LongRequestHandler(tornado.web.RequestHandler):
    def database_callback(self):
        data = self.cur.fetchone()
        if not data:
            self.finish()
            self.cnxn.close()
        else:
            self.write(data)
            self.flush()
            tornado.ioloop.IOLoop.instance().add_callback(self.database_callback)

    @tornado.web.asynchronous
    def get(self):
        try:
            self.cnxn = pyodbc.connect(self.server, self.driver, self.table, self.uid, self.pwd)
        except Exception, e:
            print e
            return

        try:
            self.cur = self.cnxn.execute(self.sql)
        except Exception, e:
            print e
            return

        tornado.ioloop.IOLoop.instance().add_callback(self.database_callback)

Однако стоит отметить, что каждый поставщик баз данных отличается. Насколько я понимаю, с MySQL большая часть времени / обработки будет фактически потрачена на вызов execute (), а не на циклический просмотр данных, потому что MySQL обрабатывает весь запрос и возвращает полный набор результатов. Если вы используете провайдера базы данных, который делает то же самое, вам может потребоваться обрабатывать подобные запросы в рабочем процессе, стоящем за Tornado.

Редактировать Мой пример только для примера. В действительности, вы захотите проверить свой обратный вызов и, возможно, перебрать довольно много строк перед возвратом, в противном случае вы тратите очень много времени ЦП на переключение между функциями в цикле ввода-вывода, а не на обработку запроса. После некоторого тестирования то, чего я боялся в отношении MySQL, оказалось правдой - именно оператор execute / query вызвал блокировку, поэтому это решение действительно не поможет в таких обстоятельствах.

...