Piszę aplikację Python + GObject, która po uruchomieniu musi odczytać nietrywialną ilość danych z dysku. Dane są odczytywane synchronicznie i zakończenie operacji odczytu zajmuje około 10 sekund, w tym czasie ładowanie interfejsu użytkownika jest opóźnione.
Chciałbym uruchomić zadanie asynchronicznie i otrzymać powiadomienie, gdy będzie gotowe, bez blokowania interfejsu użytkownika, mniej więcej tak:
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()
W przeszłości używałem GTask do tego typu rzeczy, ale martwię się, że jego kod nie został zmieniony od 3 lat, nie mówiąc już o przeniesieniu go do GObject Introspection. Co najważniejsze, nie jest już dostępny w Ubuntu 12.04. Szukam więc łatwego sposobu na asynchroniczne uruchamianie zadań, albo w standardowy sposób w Pythonie, albo w standardowy sposób GObject / GTK +.
Edycja: oto kod z przykładem tego, co próbuję zrobić. Próbowałem, python-defer
jak sugerowano w komentarzach, ale nie udało mi się uruchomić asynchronicznie długiego zadania i pozwolić, aby interfejs użytkownika ładował się bez konieczności oczekiwania na zakończenie. Przeglądaj kod testowy .
Czy istnieje prosty i szeroko stosowany sposób uruchamiania zadań asynchronicznych i otrzymywania powiadomień o ich zakończeniu?
źródło
async_call
funkcja może być tym, czego potrzebuję. Czy mógłbyś rozwinąć ją nieco i dodać odpowiedź, abym mógł ją zaakceptować i podziękować po przetestowaniu? Dzięki!Odpowiedzi:
Twój problem jest bardzo częsty, dlatego istnieje mnóstwo rozwiązań (szopy, kolejki z wieloprocesowością lub wątkami, pule pracowników, ...)
Ponieważ jest to tak powszechne, istnieje również wbudowane rozwiązanie Pythona (w wersji 3.2, ale w wersji backported tutaj: http://pypi.python.org/pypi/futures ) o nazwie concurrent.futures. „Futures” są dostępne w wielu językach, dlatego python nazywa je tak samo. Oto typowe wywołania (i tutaj jest twój pełny przykład , jednak część db jest zastąpiona przez sleep, zobacz poniżej dlaczego).
Przejdźmy teraz do twojego problemu, który jest o wiele bardziej skomplikowany niż sugeruje Twój prosty przykład. Zasadniczo masz wątki lub procesy do rozwiązania tego problemu, ale oto dlaczego twój przykład jest tak skomplikowany:
slow_load
z bazy danych, nie są wybierane, co oznacza, że nie można ich po prostu przekazywać między procesami. Zatem: brak przetwarzania wieloprocesorowego z wynikami centrum oprogramowania!print
, bez zmian stanu GTK, z wyjątkiem dodania oddzwaniania!threads_init
, a jeśli wywołasz metodę gtk lub podobną, musisz ją zabezpieczyć (we wcześniejszych wersjach była togtk.gdk.threads_enter()
,gtk.gdk.threads_leave()
patrz np. Gstreamer: http://pygstdocs.berlios.de/pygst-tutorial/playbin. HTML ).Mogę dać ci następującą sugestię:
slow_load
aby zwrócić wybieralne wyniki i korzystać z kontraktów terminowych.Jako uwaga: rozwiązań podanych przez innych (
Gio.io_scheduler_push_job
,async_call
) zrobić pracę ztime.sleep
, ale nie zsoftwarecenter.db
. Jest tak, ponieważ wszystko sprowadza się do wątków lub procesów i wątków, które nie działają z gtk isoftwarecenter
.źródło
Oto kolejna opcja przy użyciu harmonogramu we / wy GIO (nigdy wcześniej nie korzystałem z niego w Pythonie, ale poniższy przykład wydaje się działać dobrze).
źródło
Możesz także użyć GLib.idle_add (oddzwanianie), aby wywołać długo działające zadanie, gdy GLib Mainloop zakończy wszystkie zdarzenia o wyższym priorytecie (które moim zdaniem obejmują zbudowanie interfejsu użytkownika).
źródło
callback
zostanie wywołany, będzie to zrobione synchronicznie, blokując w ten sposób interfejs użytkownika, prawda?idle_add
tym, że wartość zwracana oddzwonienia ma znaczenie. Jeśli to prawda, zostanie ponownie wywołane.Użyj introspekowanego
Gio
interfejsu API, aby odczytać plik za pomocą jego metod asynchronicznych, a podczas wykonywania pierwszego wywołania wykonaj go jako limit czasu, wGLib.timeout_add_seconds(3, call_the_gio_stuff)
którymcall_the_gio_stuff
funkcja jest zwracanaFalse
.W tym przypadku konieczne jest przekroczenie limitu czasu (może być wymagana inna liczba sekund), ponieważ chociaż wywołania asynchroniczne Gio są asynchroniczne, nie są one blokujące, co oznacza, że duża aktywność dysku podczas odczytu dużego pliku lub dużego liczba plików, może spowodować zablokowanie interfejsu użytkownika, ponieważ interfejs użytkownika i operacje we / wy są nadal w tym samym (głównym) wątku.
Jeśli chcesz napisać własne funkcje, które mają być asynchroniczne, i zintegrować je z główną pętlą, używając interfejsów API we / wy pliku Pythona, musisz napisać kod jako GObject lub przekazać wywołania zwrotne lub użyć,
python-defer
aby ci pomóc Zrób to. Ale najlepiej jest używać Gio tutaj, ponieważ może przynieść wiele fajnych funkcji, szczególnie jeśli robisz otwieranie / zapisywanie plików w UX.źródło
Gio
interfejsu API. Zastanawiałem się, czy istnieje sposób na asynchroniczne uruchamianie dowolnego ogólnego długowiecznego zadania w taki sam sposób, jak robił to GTask.Myślę, że należy zauważyć, że jest to skomplikowany sposób robienia tego, co sugerował @mhall.
Zasadniczo masz uruchom to, a następnie uruchom funkcję async_call.
Jeśli chcesz zobaczyć, jak to działa, możesz grać z wyłącznikiem czasowym i klikać przycisk. Jest to w zasadzie to samo co odpowiedź @ mhall, tyle że przykładowy kod.
Na podstawie tego, co nie jest moją pracą.
Dodatkowa uwaga: musisz pozwolić drugiemu wątkowi skończyć się, zanim zostanie poprawnie zakończony lub sprawdzić, czy plik.lock w twoim wątku potomnym.
Edytuj, aby skomentować komentarz:
Początkowo zapomniałem
GObject.threads_init()
. Najwyraźniej kiedy przycisk zadziałał, zainicjował on dla mnie wątki. To zamaskowało dla mnie błąd.Ogólnie przepływ polega na utworzeniu okna w pamięci, natychmiast uruchom drugi wątek, gdy wątek się zakończy zaktualizuj przycisk. Dodałem dodatkowy sen, zanim zadzwoniłem do Gtk.main, aby sprawdzić, czy pełna aktualizacja MOGŁA działać, zanim okno zostanie jeszcze narysowane. Skomentowałem to również, aby sprawdzić, czy uruchomienie wątku wcale nie utrudnia rysowania okien.
źródło
slow_load
się, że zostanie wykonany wkrótce po uruchomieniu interfejsu użytkownika, ale wydaje się, że nie jest wywoływany, chyba że przycisk zostanie kliknięty, co trochę mnie dezorientuje, ponieważ myślałem, że celem tego przycisku było jedynie zapewnienie wizualnego wskazania stanu zadania.async_call
w tym przykładzie działa dla mnie, ale powoduje chaos, gdy portuję go do mojej aplikacji i dodam prawdziwąslow_load
funkcję, którą mam.