Я пишу Python + приложение GObject, которое должно считать нетривиальный объем данных из диска на запуск. Данные считаны синхронно, и требуется приблизительно 10 секунд для окончания операции чтения, за это время загрузка UI задержана.
Я хотел бы выполнить задачу асинхронно и получить уведомление, когда это готово, не блокируя UI, более или менее как:
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. Самое главное это больше не доступно в Ubuntu 12.04. Таким образом, я ищу простой способ выполнить задачи асинхронно, или в стандартном Python путь или в GObject/GTK + стандартный путь.
Править: вот некоторый код с примером того, что я пытаюсь сделать. Я попробовал python-defer
как предложено в комментариях, но мне не могло удаться выполнить долгую задачу асинхронно и позволить UI загрузиться, не имея необходимость ожидать его для окончания. Просмотрите тестовый код.
Существует ли легкий и широко используемый способ выполнить асинхронные задачи, и будьте уведомлены, когда они закончены?
Вы также можете использовать GLib.idle_add (callback), чтобы вызвать долгосрочную задачу, как только GLib Mainloop завершит все события с более высоким приоритетом (что, я считаю, включает создание пользовательского интерфейса).
Используйте интроспективный API Gio
для чтения файла с его асинхронными методами, а при выполнении начального вызова делайте это как тайм-аут с GLib.timeout_add_seconds(3, call_the_gio_stuff)
, где call_the_gio_stuff
- функция, которая возвращает False
. [ 115]
Здесь необходимо добавить время ожидания (хотя может потребоваться другое количество секунд), поскольку асинхронные вызовы Gio асинхронны, но не неблокированы, а это означает, что при чтении большого файла на жестком диске или большое количество файлов может привести к блокировке пользовательского интерфейса, так как пользовательский интерфейс и ввод-вывод все еще находятся в одном (основном) потоке.
Если вы хотите написать свои собственные функции, которые должны быть асинхронными, и интегрироваться с основным циклом, используя API-интерфейсы файлового ввода / вывода Python, вам придется написать код как GObject или передать обратные вызовы или использовать python-defer
, чтобы помочь вам сделать это. Но лучше использовать Gio здесь, так как он может принести вам много приятных функций, особенно если вы делаете открытия / сохранения файлов в UX.
Вот еще один вариант использования планировщика ввода-вывода GIO (я никогда раньше не использовал его в Python, но приведенный ниже пример работает нормально).
from gi.repository import GLib, Gio, GObject
import time
def slow_stuff(job, cancellable, user_data):
print "Slow!"
for i in xrange(5):
print "doing slow stuff..."
time.sleep(0.5)
print "finished doing slow stuff!"
return False # job completed
def main():
GObject.threads_init()
print "Starting..."
Gio.io_scheduler_push_job(slow_stuff, None, GLib.PRIORITY_DEFAULT, None)
print "It's running async..."
GLib.idle_add(ui_stuff)
GLib.MainLoop().run()
def ui_stuff():
print "This is the UI doing stuff..."
time.sleep(1)
return True
if __name__ == '__main__':
main()
Ваша проблема очень распространена, поэтому существует множество решений (сараи, очереди с многопроцессорной обработкой или многопоточностью, рабочие пулы, ...)
Поскольку это так часто встречается, есть и питон встроенное решение (в 3.2, но перенесено сюда: http://pypi.python.org/pypi/futures ) под названием concurrent.futures. «Фьючерсы» доступны на многих языках, поэтому python называет их одинаковыми. Вот типичные вызовы (и вот ваш полный пример , однако часть db заменена на sleep, см. Ниже, почему).
from concurrent import futures
executor = futures.ProcessPoolExecutor(max_workers=1)
#executor = futures.ThreadPoolExecutor(max_workers=1)
future = executor.submit(slow_load)
future.add_done_callback(self.on_complete)
Теперь к вашей проблеме, которая намного сложнее, чем предлагает ваш простой пример. В общем, у вас есть потоки или процессы для решения этой проблемы, но вот почему ваш пример так сложен:
slow_load
из БД, нельзя перехватывать, что означает, что их нельзя просто передавать между процессами. Итак: нет многопроцессорной обработки с результатами softwarecenter! 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
.
Думаю, стоит отметить, что это запутанный способ сделать то, что предложил @mhall.
По сути, вы запустили это, а затем запустите эту функцию async_call.
Если вы хотите посмотреть, как это работает, вы можете поиграть с таймером сна и продолжать нажимать кнопку. По сути, это то же самое, что и ответ @ mhall, за исключением того, что есть пример кода.
На основании этого , что не моя работа.
import threading
import time
from gi.repository import Gtk, GObject
# calls f on another thread
def async_call(f, on_done):
if not on_done:
on_done = lambda r, e: None
def do_call():
result = None
error = None
try:
result = f()
except Exception, err:
error = err
GObject.idle_add(lambda: on_done(result, error))
thread = threading.Thread(target = do_call)
thread.start()
class SlowLoad(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Hello World")
GObject.threads_init()
self.connect("delete-event", Gtk.main_quit)
self.button = Gtk.Button(label="Click Here")
self.button.connect("clicked", self.on_button_clicked)
self.add(self.button)
self.file_contents = 'Slow load pending'
async_call(self.slow_load, self.slow_complete)
def on_button_clicked(self, widget):
print self.file_contents
def slow_complete(self, results, errors):
'''
'''
self.file_contents = results
self.button.set_label(self.file_contents)
self.button.show_all()
def slow_load(self):
'''
'''
time.sleep(5)
self.file_contents = "Slow load in progress..."
time.sleep(5)
return 'Slow load complete'
if __name__ == '__main__':
win = SlowLoad()
win.show_all()
#time.sleep(10)
Gtk.main()
Дополнительное примечание: вы должны позволить другому потоку завершить работу, прежде чем он завершится должным образом, или проверить файл.lock в вашем дочернем потоке.
Изменить адрес комментария:
Первоначально я забыл GObject.threads_init()
. Очевидно, что когда кнопка сработала, она инициализировала потоки для меня. Это замаскировало ошибку для меня.
Обычно поток создает окно в памяти, немедленно запускает другой поток, когда поток завершает обновление кнопки. Я добавил дополнительный спящий режим еще до того, как позвонил в Gtk.main, чтобы убедиться, что полное обновление МОЖЕТ выполняться до того, как окно будет нарисовано. Я также прокомментировал это, чтобы убедиться, что запуск потока вообще не мешает рисованию окна.