Python GObject Introspection 앱에서 비동기 작업을 실행하는 방법


16

시작시 디스크에서 사소한 양의 데이터를 읽어야하는 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 를 사용해 왔지만 GObject Introspection으로 포팅되지는 않았지만 코드가 3 년 동안 만지지 않은 것에 대해 걱정하고 있습니다. 가장 중요한 것은 우분투 12.04에서 더 이상 사용할 수 없다는 것입니다. 따라서 표준 Python 방식이나 GObject / GTK + 표준 방식으로 작업을 비동기식으로 쉽게 실행할 수있는 방법을 찾고 있습니다.

편집 : 여기 내가하려는 일의 예가있는 코드가 있습니다. python-defer의견에서 제안한대로 시도했지만 긴 작업을 비동기 적으로 실행하고 UI가 끝나기를 기다릴 필요없이로드 할 수 없었습니다. 테스트 코드를 찾아보십시오 .

비동기 작업을 쉽고 광범위하게 실행하는 방법이 있고 완료되면 알림을 받습니까?


이것은 좋은 예는 아니지만, 이것이 당신이 찾고있는 것이라 확신
RobotHumans

쿨, 나는 당신의 async_call기능이 내가 필요한 것일 것이라고 생각합니다 . 조금 확장하고 답변을 추가하여 테스트 후 수락하고 크레딧을 줄 수 있습니까? 감사!
David Planella

1
좋은 질문, 매우 유용합니다! ;-)
Rafał Cieślak

답변:


15

문제는 매우 일반적인 문제이므로 많은 솔루션 (창고, 멀티 프로세싱 또는 스레딩 대기열, 작업자 풀 등)이 있습니다.

너무 흔하기 때문에 python build-in 솔루션 (3.2에서는 있지만 여기서는 http://pypi.python.org/pypi/futures )이 있습니다. '미래'는 여러 언어로 제공되므로 파이썬은 그것들을 동일하게 부릅니다. 다음은 일반적인 호출입니다 (여기에는 전체 예제 가 있지만 db 부분은 절전 모드로 대체됩니다 (아래 이유 참조)).

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)

이제 간단한 예제에서 제안하는 것보다 훨씬 복잡한 문제가 있습니다. 일반적으로이 문제를 해결하기위한 스레드 또는 프로세스가 있지만 다음은 예제가 너무 복잡한 이유입니다.

  1. 대부분의 Python 구현에는 스레드 멀티 코어를 완전히 활용 하지 못하게 하는 GIL이 있습니다 . 따라서 파이썬으로 스레드를 사용하지 마십시오!
  2. slow_loadDB에서 반환하려는 객체 는 피클 링 할 수 없으므로 프로세스간에 간단히 전달할 수 없습니다. 따라서 소프트웨어 센터 결과로 멀티 프로세싱이 필요 없습니다!
  3. 호출하는 라이브러리 (softwarecenter.db)는 스레드 안전하지 않습니다 (gtk 또는 이와 유사한 것으로 간주).이 메소드를 스레드에서 호출하면 이상한 동작이 발생합니다 (내 테스트에서 '코어 덤프'를 통한 '작동하는 것'부터 간단한 것까지의 모든 것) 결과없이 종료). 따라서 소프트웨어 센터가있는 스레드가 없습니다.
  4. gtk의 모든 비동기 콜백 은 glib 메인 루프에서 호출 될 콜백을 셰이드하는 외에는 아무것도 하지 않아야합니다 . 따라서 : print콜백 추가를 제외하고 gtk 상태 변경 없음!
  5. Gtk 및 이와 유사한 기능은 즉시 사용 가능한 스레드에서 작동하지 않습니다. 당신은 할 필요가 threads_init, 그리고 당신은 GTK 또는 모두 메서드를 호출 할 경우, 당신은이이었다 이전 버전에서 그 방법을 (보호해야 gtk.gdk.threads_enter(), gtk.gdk.threads_leave()예를 들어 gstreamer를 위해 참조하십시오. http://pygstdocs.berlios.de/pygst-tutorial/playbin합니다. html ).

나는 당신에게 다음과 같은 제안을 할 수 있습니다 :

  1. 선택 slow_load가능한 결과를 반환하고 프로세스와 함께 미래를 사용하도록 다시 작성하십시오 .
  2. 소프트웨어 센터에서 python-apt 또는 이와 유사한 것으로 전환하십시오 (아마 마음에 들지 않을 것입니다). 그러나 Canonical에서 고용 한 이후 소프트웨어 센터 개발자에게 소프트웨어 에 문서추가 (예 : 스레드 안전하지 않다고 명시)하고 더 나은 소프트웨어 센터 스레드 안전 을 추가 하도록 요청할 수 있습니다 .

참고로 다른 사람 ( Gio.io_scheduler_push_job, async_call) 제공 한 솔루션 작동 time.sleep하지만 작동 하지 않습니다 softwarecenter.db. 이것은 gtk 및로 작동하지 않기 위해 스레드 또는 프로세스 및 스레드로 모두 요약되기 때문 softwarecenter입니다.


감사! 나는 그것이 왜 그것이 불가능한지를 매우 자세하게 지적하면서 당신의 대답을 받아 들일 것입니다. 불행히도, 내 응용 프로그램에서 Ubuntu 12.04 용으로 패키지되지 않은 소프트웨어를 사용할 수 없습니다 ( launchpad.net/ubuntu/+source/python-concurrent.futures 이지만 Quantal 용 ) 내 작업을 비동기식으로 실행합니다. 소프트웨어 센터 개발자들에게 이야기를 메모에 관해서는, 나는 코드와 문서 또는 :-) 그들에게 이야기에 대한 변경 기여할 수있는 자원 봉사와 같은 위치에있어
데이비드 Planella

IO 동안 GIL이 릴리스되므로 스레드를 사용하는 것이 좋습니다. 비동기 IO가 사용되는 경우에는 필요하지 않습니다.
jfs

10

다음은 GIO의 I / O 스케줄러를 사용하는 또 다른 옵션입니다 (파이썬에서는 이전에 사용한 적이 없지만 아래 예제는 제대로 실행되는 것 같습니다).

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()

slow_stuff가 완료된 후 메인 스레드에서 무언가를 실행하려면 GIO.io_scheduler_job_send_to_mainloop ()를 참조하십시오.
Siegfried Gevatter

답변과 예제에 대해 Sigfried에게 감사드립니다. 불행히도, 현재 작업에서 Gio API를 사용하여 비동기식으로 실행할 기회가없는 것 같습니다.
David Planella

이건 정말 유용하지만, 지금까지 내가 Gio.io_scheduler_job_send_to_mainloop가 :( 파이썬에 존재하지 않는 말할 수있는 등
SIL

2

GLib Mainloop가 우선 순위가 높은 모든 이벤트를 완료하면 (GUI 빌드 포함) 믿고 장기 실행 작업을 호출하기 위해 GLib.idle_add (callback)을 사용할 수도 있습니다.


고마워 마이크. 예, UI가 준비되면 작업을 시작하는 데 도움이됩니다. 그러나 반면에, 나는 callback호출 될 때 동 기적으로 수행되어 UI를 차단 한다는 것을 이해합니다 .
David Planella

idle_add는 그렇게 작동하지 않습니다. idle_add에서 호출을 차단하는 것은 여전히 ​​나쁜 일이며 UI 업데이트가 발생하지 않도록합니다. 심지어 비동기 API조차도 UI를 차단하는 유일한 방법은 백그라운드 스레드에서 수행하는 것입니다.
dobey

이상적으로 느린 작업을 청크로 분할하여 유휴 콜백에서 약간의 작업을 실행하고 반환 (및 UI 콜백과 같은 다른 항목을 실행)하고 콜백이 다시 호출되면 더 많은 작업을 계속할 수 있습니다. 의 위에.
Siegfried Gevatter

단점 idle_add은 콜백의 반환 값이 중요하다는 것입니다. 사실이면 다시 호출됩니다.
Flimm

2

내부 검사 된 GioAPI를 사용하여 비동기 메소드로 파일을 읽고 초기 호출을 수행 할 때 GLib.timeout_add_seconds(3, call_the_gio_stuff)where call_the_gio_stuff가 리턴하는 함수가있는 시간 종료로 수행하십시오 False.

Gio 비동기 호출이 비동기 적이지만 비 블로킹이 아니기 때문에 추가 시간이 필요합니다. UI와 I / O가 여전히 동일한 (메인) 스레드에 있기 때문에 파일 수가 많으면 UI가 차단 될 수 있습니다.

파이썬의 파일 I / O API를 사용하여 비동기식으로 자신의 함수를 작성하고 메인 루프와 통합하려면 코드를 GObject로 작성하거나 콜백을 전달하거나 python-defer도움을 주어야 합니다. 해. 그러나 Gio를 사용하는 것이 가장 좋습니다. 특히 UX에서 파일 열기 / 저장 작업을 수행하는 경우 많은 훌륭한 기능을 제공 할 수 있습니다.


감사합니다 @dobey. 실제로 디스크에서 직접 파일을 읽지 않고 있습니다. 원래 게시물에서 더 명확하게 만들어야했을 것입니다. 내가 실행하는 장기 실행 작업은 askubuntu.com/questions/139032/…에 대한 답변에 따라 Software Center 데이터베이스를 읽는 중이므로Gio API를 사용할 수 있는지 잘 모르겠습니다 . 내가 궁금한 것은 GTask와 같은 방식으로 일반적인 장기 실행 작업을 비동기식으로 실행할 수 있는지 여부입니다.
David Planella

나는 GTask가 정확히 무엇인지 알지 못하지만 gtask.sourceforge.net을 의미 한다면 그것을 사용해야한다고 생각하지 않습니다. 그것이 다른 것이면, 나는 그것이 무엇인지 모른다. 그러나 언급 한 두 번째 경로를 가져 와서 해당 코드를 래핑하기 위해 비동기 API를 구현하거나 스레드에서 모두 수행 해야하는 것처럼 들립니다.
dobey

질문에 링크가 있습니다. GTask는 다음과 같습니다 : chergert.github.com/gtask
David Planella

1
아, 그것은 python-defer (및 뒤틀린 지연 API)에서 제공하는 API와 매우 유사합니다. 아마도 python-defer를 사용해야합니까?
dobey

1
예를 들어 GLib.idle_add ()를 사용하여 주요 우선 순위 이벤트가 발생할 때까지 호출되는 것을 지연시켜야합니다. : 이것처럼 pastebin.ubuntu.com/1011660
dobey

1

나는 이것이 @ 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()

추가로, 다른 스레드가 올바르게 종료되거나 하위 스레드에서 file.lock을 확인하기 전에 다른 스레드가 완료되도록해야합니다.

댓글 주소를 수정 :
처음에 잊어 버렸습니다 GObject.threads_init(). 분명히 버튼이 작동했을 때 스레딩이 초기화되었습니다. 이것은 나를 위해 실수를 가렸다.

일반적으로 흐름은 메모리에 창을 만들고 스레드가 버튼 업데이트를 완료하면 즉시 다른 스레드를 시작합니다. 창을 열기 전에 전체 업데이트 COULD가 실행되는지 확인하기 위해 Gtk.main을 호출하기 전에 추가 절전 모드를 추가했습니다. 또한 스레드 시작이 창 그리기를 전혀 방해하지 않는지 확인하기 위해 주석을 달았습니다.


1
감사. 나는 그것을 따를 수 있는지 확실하지 않습니다. 하나 slow_load는 UI가 시작된 직후에 실행될 것이지만 버튼을 클릭하지 않으면 호출되지 않는 것 같습니다. 버튼의 목적은 시각적 표시를 제공하는 것이라고 생각했기 때문에 조금 혼란 스럽습니다. 작업 상태.
David Planella

죄송합니다. 한 줄이 빠졌습니다. 그랬어. 스레드를 준비하도록 GObject에 지시하는 것을 잊었습니다.
RobotHumans 16:27에

그러나 스레드에서 메인 루프를 호출하면 문제가 발생할 수 있지만 실제 작업을 수행하지 않는 사소한 예제에서는 쉽게 노출되지 않을 수 있습니다.
dobey

유효한 점은,하지만 난 사소한 예 (I가 아닌 사소한 응용 프로그램은 일을해야한다고 생각) DBUS를 통해 통지를 전송받을만한 생각하지 않았다
RobotHumans

흠, async_call이 예제에서 실행 하면 효과가 있지만 앱에 이식하면 실제 slow_load기능이 추가 됩니다.
David Planella
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.