Я пишу приложение Python + GObject, которое должно при запуске читать нетривиальный объем данных с диска. Данные считываются синхронно, и для завершения операции чтения требуется около 10 секунд, в течение которых загрузка пользовательского интерфейса задерживается.
Я хотел бы выполнить задачу асинхронно и получить уведомление, когда она будет готова, без блокировки пользовательского интерфейса, более или менее, например:
def take_ages():
read_a_huge_file_from_disk()
def on_finished_long_task():
print "Finished!"
run_long_task(task=take_ages, callback=on_finished_long_task)
load_the_UI_without_blocking_on_long_task()
В прошлом я использовал GTask для такого рода вещей, но я обеспокоен тем, что его код не затрагивался в течение 3 лет, не говоря уже о переносе в GObject Introspection. Самое главное, он больше не доступен в Ubuntu 12.04. Поэтому я ищу простой способ выполнения задач асинхронно, либо стандартным способом Python, либо стандартным способом GObject / GTK +.
Редактировать: вот код с примером того, что я пытаюсь сделать. Я попытался, python-defer
как предложено в комментариях, но мне не удалось запустить длинную задачу асинхронно и позволить загрузке пользовательского интерфейса, не дожидаясь ее завершения. Просмотрите код теста .
Существует ли простой и широко используемый способ запуска асинхронных задач и получения уведомлений по их окончании?
источник
async_call
функция может быть то, что мне нужно Не могли бы вы немного рассказать об этом и добавить ответ, чтобы я мог принять его и поставить вам кредит после проверки? Благодарность!Ответы:
Ваша проблема является очень распространенной, поэтому существует множество решений (сараи, очереди с многопроцессорной обработкой или многопоточностью, рабочие пулы, ...)
Поскольку это так часто встречается, существует также встроенное решение для Python (в 3.2, но здесь есть обратная связь: http://pypi.python.org/pypi/futures ), называемое concurrent.futures. «Фьючерсы» доступны на многих языках, поэтому python называет их одинаковыми. Вот типичные вызовы (и вот ваш полный пример , однако, часть db заменена на sleep, см. Ниже, почему).
Теперь к вашей проблеме, которая намного сложнее, чем предлагает ваш простой пример. В общем, у вас есть потоки или процессы, чтобы решить эту проблему, но вот почему ваш пример такой сложный:
slow_load
из БД, нельзя перехватывать, что означает, что их нельзя просто передавать между процессами. Итак: нет многопроцессорной обработки с результатами программного центра!print
, никаких изменений состояния GTK, кроме добавления обратного вызова!threads_init
, и если вы вызываете метод gtk или аналогичный, вы должны защитить этот метод (в более ранних версиях это былоgtk.gdk.threads_enter()
,gtk.gdk.threads_leave()
см., Например, gstreamer: http://pygstdocs.berlios.de/pygst-tutorial/playbin. HTML ).Я могу дать вам следующее предложение:
slow_load
чтобы получить результаты, которые можно использовать для поиска, и использовать фьючерсы с процессами.Как примечание: решения, предоставленные другими (
Gio.io_scheduler_push_job
,async_call
) , работают с,time.sleep
но не сsoftwarecenter.db
. Это потому, что все сводится к потокам или процессам и потокам, которые не работают с gtk иsoftwarecenter
.источник
Вот еще один вариант использования планировщика ввода-вывода GIO (я никогда раньше не использовал его в Python, но приведенный ниже пример работает нормально).
источник
Вы также можете использовать GLib.idle_add (callback), чтобы вызвать долгосрочную задачу, когда GLib Mainloop завершит все события с более высоким приоритетом (что, я считаю, включает в себя создание пользовательского интерфейса).
источник
callback
вызове это делается синхронно, блокируя пользовательский интерфейс, верно?idle_add
в том, что возвращаемое значение обратного вызова имеет значение. Если это правда, он будет вызван снова.Используйте интроспектированный
Gio
API для чтения файла с его асинхронными методами, и при выполнении первоначального вызова, сделать это как тайм - аут с ,GLib.timeout_add_seconds(3, call_the_gio_stuff)
гдеcall_the_gio_stuff
есть функция , которая возвращаетFalse
.Здесь необходимо добавить время ожидания (хотя может потребоваться иное количество секунд), поскольку асинхронные вызовы Gio асинхронны, но не неблокированы, а это означает, что при чтении большого файла или большого объема данных на жестком диске Количество файлов, может привести к блокировке пользовательского интерфейса, так как пользовательский интерфейс и ввод-вывод все еще находятся в одном (основном) потоке.
Если вы хотите написать свои собственные функции, которые должны быть асинхронными, и интегрироваться с основным циклом, используя API-интерфейсы файлового ввода / вывода Python, вам придется написать код как GObject или передать обратные вызовы или использовать их,
python-defer
чтобы помочь вам. сделай это. Но лучше использовать Gio здесь, так как он может принести вам много полезных функций, особенно если вы делаете открытия / сохранения файлов в UX.источник
Gio
API. Что мне было интересно, так это то, существует ли способ асинхронно запускать какую-либо общую долгосрочную задачу точно так же, как это делал GTask.Я думаю, стоит отметить, что это запутанный способ сделать то, что предложил @mhall.
По сути, вы должны запустить это, а затем запустить эту функцию async_call.
Если вы хотите увидеть, как это работает, вы можете поиграть с таймером сна и продолжать нажимать кнопку. По сути, это то же самое, что и ответ @ mhall, за исключением того, что есть пример кода.
Исходя из этого, это не моя работа.
Дополнительное примечание: вы должны позволить другому потоку завершить работу, прежде чем он завершится должным образом, или проверить файл.lock в вашем дочернем потоке.
Изменить по адресу комментарий:
Изначально я забыл
GObject.threads_init()
. Очевидно, что когда кнопка сработала, она инициализировала потоки для меня. Это замаскировало ошибку для меня.Обычно поток создает окно в памяти, немедленно запускает другой поток, когда поток завершает обновление кнопки. Я добавил дополнительный спящий режим еще до того, как позвонил в Gtk.main, чтобы убедиться, что полное обновление МОЖЕТ выполняться до того, как окно будет нарисовано. Я также прокомментировал это, чтобы убедиться, что запуск потока вообще не мешает рисованию окна.
источник
slow_load
что он будет выполнен вскоре после запуска пользовательского интерфейса, но, кажется, он никогда не вызывается, если только кнопка не нажата, что немного смущает меня, так как я думал, что цель кнопки - просто обеспечить визуальную индикацию. состояния задачи.async_call
в этом примере работает для меня, но это порождает хаос, когда я портирую его в свое приложение и добавляю реальнуюslow_load
функцию, которая у меня есть.