पायथन गोब्जेक्ट इंट्रोस्पेक्शन ऐप में अतुल्यकालिक कार्यों को कैसे चलाएं


16

मैं एक पायथन + 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 में पोर्ट किया गया है। सबसे महत्वपूर्ण बात, यह उबंटू 12.04 में उपलब्ध नहीं है। इसलिए मैं कार्यों को अतुल्यकालिक रूप से चलाने का एक आसान तरीका तलाश रहा हूं, या तो मानक पायथन तरीके से या GObject / GTK + मानक तरीके से।

संपादित करें: मैं क्या करने की कोशिश कर रहा हूं, इसके उदाहरण के साथ यहां कुछ कोड है। मैंने python-deferटिप्पणियों में सुझाव के अनुसार प्रयास किया है, लेकिन मैं लंबे समय तक कार्य को अतुल्यकालिक रूप से चलाने का प्रबंधन नहीं कर सका और UI लोड को समाप्त होने के लिए इंतजार किए बिना रहने दिया। परीक्षा कोड ब्राउज़ करें

क्या अतुल्यकालिक कार्यों को चलाने का एक आसान और व्यापक रूप से उपयोग किया जाने वाला तरीका है और समाप्त होने पर उन्हें सूचित किया जाता है?


यह एक बहुत अच्छा उदाहरण नहीं है, लेकिन मुझे पूरा यकीन है कि यह वही है जो आप ढूंढ रहे हैं: raw.github.com/gist/1132418/…
RobotHumans

कूल, मुझे लगता है कि आपका async_callकार्य हो सकता है जो मुझे चाहिए। क्या आप इसे थोड़ा विस्तार देने और उत्तर जोड़ने का मन बनाएंगे, ताकि मैं इसे स्वीकार करूं और इसका परीक्षण करने के बाद आपको इसका श्रेय दूं? धन्यवाद!
डेविड प्लेनेला

1
बहुत अच्छा सवाल, बहुत उपयोगी! ;-)
राफेल सिलेक

जवाबों:


15

आपकी समस्या बहुत आम है, इसलिए टन के समाधान (शेड, मल्टीप्रोसेसिंग या थ्रेडिंग के साथ कतारें, कार्यकर्ता पूल, ...) हैं

चूँकि यह इतना सामान्य है, इसलिए एक पाइथन बिल्ड-इन सॉल्यूशन भी है (3.2 में, लेकिन यहाँ बैकपोर्ट किया गया है: http://pypi.python.org/pypi/futures ) जिसे समवर्ती.फुट्स कहा जाता है। 'फ्यूचर्स' कई भाषाओं में उपलब्ध हैं, इसलिए अजगर उन्हें एक ही कहते हैं। यहां विशिष्ट कॉल हैं (और यहां आपका पूरा उदाहरण है , हालांकि, डीबी भाग को नींद से बदल दिया जाता है, नीचे देखें क्यों)।

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. अधिकांश पायथन कार्यान्वयन में एक जीआईएल है, जो थ्रेड को पूरी तरह से मल्टीकोर्स का उपयोग नहीं करता है । तो: अजगर के साथ धागे का उपयोग न करें!
  2. slow_loadडीबी से जिन वस्तुओं को आप वापस करना चाहते हैं, वे लेने योग्य नहीं हैं, जिसका अर्थ है कि उन्हें बस प्रक्रियाओं के बीच पारित नहीं किया जा सकता है। तो: सॉफ्टवेयरकंटेरेंट परिणामों के साथ कोई मल्टीप्रोसेसिंग नहीं!
  3. लाइब्रेरी जिसे आप कहते हैं (softwarecenter.db) थ्रेडसेफ़ नहीं है (gtk या इसी तरह के शामिल करने के लिए लगता है), इसलिए इन विधियों को एक थ्रेड में कॉल करने से अजीब व्यवहार होता है (मेरे परीक्षण में, यह 'कोर डंप' से 'सरल' के लिए 'सब कुछ' पर काम करता है) परिणाम के बिना छोड़ने)। तो: सॉफ्टवेयर थ्रेड के साथ कोई सूत्र।
  4. Gtk में प्रत्येक अतुल्यकालिक कॉलबैक को कॉलबैक शेड्यूल करने के अलावा कुछ भी नहीं करना चाहिए जिसे ग्लिब मेनलूप में कहा जाएगा। तो: नहीं print, कोई gtk स्थिति नहीं बदलती, सिवाय कॉलबैक जोड़ने के!
  5. Gtk और alike बॉक्स के थ्रेड्स के साथ काम नहीं करते हैं। आप क्या करने की जरूरत threads_initहै, और यदि आप एक जीटीके या समान रूप से विधि कॉल, आपको लगता है कि विधि (पहले के संस्करणों यह था में की रक्षा के लिए है gtk.gdk.threads_enter(), gtk.gdk.threads_leave()उदाहरण के लिए gstreamer देखें:। Http://pygstdocs.berlios.de/pygst-tutorial/playbin। html )।

मैं आपको निम्नलिखित सुझाव दे सकता हूं:

  1. slow_loadचयन योग्य परिणाम वापस करने और प्रक्रियाओं के साथ वायदा का उपयोग करने के लिए अपने को फिर से लिखें।
  2. सॉफ्टवेयरसेंटर से अजगर-समान या इसी तरह स्विच करें (आप शायद ऐसा पसंद नहीं करते हैं)। लेकिन कैन्यनिकल द्वारा आपके द्वारा नियोजित किए जाने के बाद से, आप सॉफ़्टवेयरसेंटर डेवलपर्स से सीधे अपने सॉफ़्टवेयर में दस्तावेज़ जोड़ने के लिए कह सकते हैं (उदाहरण के लिए, यह बताते हुए कि यह थ्रेड सुरक्षित नहीं है) और इससे भी बेहतर, सॉफ़्टवेयर-थ्रेडस्फ़्रेफ़ बना रहा है।

एक नोट के रूप में: समाधान दूसरों के द्वारा दिए गए ( Gio.io_scheduler_push_job, async_call) कर के साथ काम time.sleepनहीं बल्कि साथ softwarecenter.db। यह है, क्योंकि यह सभी लड़ियाँ या प्रक्रियाओं और थ्रेड्स के लिए फोड़ा जाता है ताकि gtk और के साथ काम न किया जा सके softwarecenter


धन्यवाद! मैं आपके उत्तर को स्वीकार करने जा रहा हूं क्योंकि यह मुझे बहुत विस्तार से बताता है कि यह क्यों उल्लेखनीय नहीं है। दुर्भाग्य से, मैं अपने ऐप में Ubuntu 12.04 के लिए पैक किए गए सॉफ़्टवेयर का उपयोग नहीं कर सकता (यह क्वांटल के लिए है, हालांकि लॉन्चपैड. net/ubuntu/+source/python-concurrent.futures ), इसलिए मुझे लगता है कि मैं सक्षम होने के साथ फंस गया हूं। अपने कार्य को अतुल्यकालिक रूप से चलाने के लिए। सॉफ्टवेयर सेंटर डेवलपर्स से बात करने के लिए नोट के बारे में, मैं कोड और प्रलेखन में बदलाव करने या उनसे बात करने के लिए किसी भी स्वयंसेवक के रूप में उसी स्थिति में हूं। :-)
डेविड प्लानेला

IO के दौरान GIL जारी किया जाता है, इसलिए थ्रेड्स का उपयोग करना पूरी तरह से ठीक है। हालांकि यह आवश्यक नहीं है यदि async IO का उपयोग किया जाता है।
अक्टूबर को

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

GIO.io_scheduler_job_send_to_mainloop () भी देखें, यदि आप धीमे-धीमे खत्म होने के बाद मुख्य धागे में कुछ चलाना चाहते हैं।
सिगफ्रीड गेवेटर

धन्यवाद Sigfried जवाब और उदाहरण के लिए। दुर्भाग्य से, ऐसा लगता है कि मेरे वर्तमान कार्य के साथ मुझे Gio API का उपयोग करने का कोई मौका नहीं मिला है ताकि इसे अतुल्यकालिक रूप से चलाया जा सके।
डेविड प्लेनेला

यह वास्तव में उपयोगी था, लेकिन जहाँ तक मैं बता सकता हूँ Gio.io_scheduler_job_send_to_mainloop अजगर में :( मौजूद नहीं है के रूप में
सिल

2

GLI Mainloop के समाप्त होने के बाद आप लंबे समय तक चलने वाले कार्य को कॉल करने के लिए GLib.idle_add (कॉलबैक) का भी उपयोग कर सकते हैं, यह सभी उच्च प्राथमिकता वाली घटनाएं हैं (जो मुझे लगता है कि UI का निर्माण शामिल है)।


धन्यवाद माइक। हां, जब यूआई तैयार हो जाएगा तो निश्चित रूप से कार्य शुरू करने में मदद करेगा। लेकिन दूसरी ओर, मैं समझता हूं कि जब callbackबुलाया जाता है, तो इसे सिंक्रोनाइज़ किया जाएगा, इस प्रकार UI को अवरुद्ध किया जाएगा, है ना?
डेविड प्लेनेला

इस तरह से idle_add काफी काम नहीं करता है। एक निष्क्रिय_द में अवरुद्ध कॉल करना अभी भी एक बुरा काम है, और यह यूआई को अपडेट होने से रोकेगा। और यहां तक ​​कि अतुल्यकालिक एपीआई अभी भी अवरुद्ध हो सकता है, जहां यूआई और अन्य कार्यों को अवरुद्ध करने से बचने का एकमात्र तरीका यह है कि इसे एक पृष्ठभूमि थ्रेड में किया जाए।
dobey

आदर्श रूप से आप अपने धीमे काम को विखंडू में विभाजित कर देंगे, इसलिए आप इसे बेकार कॉलबैक में वापस चला सकते हैं, (और यूआई कॉलबैक की तरह अन्य सामान चला सकते हैं), अपने कॉलबैक को फिर से कॉल करने के बाद कुछ और काम करना जारी रखें, और इसी तरह पर।
सिगफ्रीड गेवेटर

इसके साथ एक गेटा idle_addयह है कि कॉलबैक का रिटर्न मान मायने रखता है। अगर यह सच है, तो इसे फिर से बुलाया जाएगा।
फ्लिम

2

Introspected का प्रयोग करें Gioएपीआई एक फ़ाइल को पढ़ने के लिए, अपने अतुल्यकालिक तरीकों के साथ, और जब प्रारंभिक कॉल करने, के साथ एक समय समाप्ति के रूप में यह कर GLib.timeout_add_seconds(3, call_the_gio_stuff)जहां call_the_gio_stuffएक समारोह जो रिटर्न है False

यहाँ जोड़ने के लिए समयबाह्य (सेकंड की एक अलग संख्या की आवश्यकता हो सकती है, यद्यपि), क्योंकि जब जियो एसिंक्स कॉल अतुल्यकालिक होते हैं, तो वे गैर-अवरुद्ध नहीं होते हैं, जिसका अर्थ है कि किसी बड़ी फ़ाइल को पढ़ने की भारी डिस्क गतिविधि, या बड़ी फ़ाइलों की संख्या, अवरुद्ध UI में परिणाम कर सकती है, क्योंकि UI और I / O अभी भी एक ही (मुख्य) धागे में हैं।

यदि आप अपने स्वयं के कार्यों को async होना चाहते हैं, और मुख्य लूप के साथ एकीकृत करते हैं, तो पायथन फाइल I / O API का उपयोग करके, आपको कोड को एक GObject के रूप में लिखना होगा, या कॉलबैक पास करने के लिए, या python-deferआपकी सहायता करने के लिए उपयोग करना होगा। कर दो। लेकिन यहाँ Gio का उपयोग करना सबसे अच्छा है, क्योंकि यह आपके लिए बहुत सारी अच्छी सुविधाएँ ला सकता है, खासकर यदि आप UX में फाइल को ओपन / सेव कर रहे हैं।


धन्यवाद @dobey मैं वास्तव में डिस्क से एक फ़ाइल को सीधे नहीं पढ़ रहा हूं, मुझे संभवतः मूल पोस्ट में स्पष्ट करना चाहिए। जो लंबे समय से मैं काम कर रहा हूं, वह सॉफ्टवेयर सेंटर डेटाबेस से पूछ रहा है, क्योंकि यह पूछने के लिए कि क्या मैं Gioएपीआई का उपयोग कर सकता हूं, यह पूछने के लिए उत्तर के अनुसार सॉफ्टवेयर सेंटर डेटाबेस से पूछें । जो मैं सोच रहा था कि क्या कोई सामान्य जेनेरिक लॉन्ग-रनिंग टास्क को चलाने का एक तरीका है, जैसा कि GTask करता था।
डेविड प्लेनेला

मुझे नहीं पता कि GTask वास्तव में क्या है, लेकिन अगर आपका मतलब gtask.sourceforge.net है तो मुझे नहीं लगता कि आपको इसका उपयोग करना चाहिए। अगर यह कुछ और है, तो मुझे नहीं पता कि यह क्या है। लेकिन ऐसा लगता है कि आपको मेरे द्वारा बताए गए दूसरे मार्ग को लेना होगा, और उस कोड को लपेटने के लिए कुछ अतुल्यकालिक एपीआई को लागू करना होगा, या बस एक धागे में यह सब करना होगा।
dobey

प्रश्न में इसकी एक कड़ी है। GTask है (है): chergert.github.com/gtask
डेविड प्लेनेला

1
आह, यह अजगर-डेफर द्वारा प्रदान की गई एपीआई (और मुड़ डिफर्ड एपीआई) के समान दिखता है। शायद आपको पायथन-डेफर का उपयोग करना चाहिए?
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()

अतिरिक्त ध्यान दें, आपको दूसरे धागे को ठीक से पूरा करने देना होगा, इससे पहले कि वह ठीक से समाप्त हो जाए या आपके बच्चे के धागे में फ़ाइल के लिए जाँच करें।

टिप्पणी को संपादित करने के लिए संपादित करें:
शुरू में मैं भूल गया था GObject.threads_init()। जाहिर है जब बटन निकाल दिया गया, तो यह मेरे लिए थ्रेडिंग इनिशियलाइज़ हो गया। इसने मेरे लिए गलती का सामना किया।

आम तौर पर प्रवाह मेमोरी में विंडो बनाता है, तुरंत थ्रेड को लॉन्च करता है, जब थ्रेड पूरा होता है बटन अपडेट करें। इससे पहले कि मैं भी Gtk.main को बुलाया, यह सत्यापित करने के लिए मैंने एक अतिरिक्त नींद जोड़ी कि सत्यापित करें कि विंडो से पहले भी पूर्ण अपडेट COULD चलता है। मैंने यह सत्यापित करने के लिए भी टिप्पणी की कि थ्रेड लॉन्च विंडो ड्रॉइंग को बिल्कुल भी बाधित नहीं करता है।


1
धन्यवाद। मुझे यकीन नहीं है कि मैं इसका पालन कर सकता हूं। एक के लिए, मुझे उम्मीद slow_loadहै कि यूआई शुरू होने के तुरंत बाद निष्पादित किया जाएगा, लेकिन यह तब तक कभी नहीं बुलाया जाएगा, जब तक कि बटन पर क्लिक नहीं किया जाता है, जो मुझे थोड़ा भ्रमित करता है, जैसा कि मैंने सोचा था कि बटन का उद्देश्य सिर्फ दृश्य संकेत प्रदान करना था कार्य की स्थिति।
डेविड प्लेनेला

क्षमा करें, मुझे एक पंक्ति याद आ गई। यही किया। मैं धागे के लिए तैयार होने के लिए GObject को बताना भूल गया।
रोबॉटहैंस

लेकिन आप एक धागे से मुख्य लूप में कॉल कर रहे हैं, जिससे समस्याएं हो सकती हैं, हालांकि वे आपके तुच्छ उदाहरण में आसानी से उजागर नहीं हो सकते हैं जो कोई वास्तविक काम नहीं करता है।
dobey

मान्य बिंदु, लेकिन मुझे नहीं लगा कि एक तुच्छ उदाहरण डीबस के माध्यम से अधिसूचना भेजने का विलय किया गया है (जो मुझे लगता है कि एक गैर-तुच्छ ऐप करना चाहिए)
रोबॉटहैंस

एचएम, async_callइस उदाहरण में चल रहा है मेरे लिए काम करता है, लेकिन यह तबाही लाता है जब मैं इसे अपने ऐप में पोर्ट करता हूं और मुझे जो वास्तविक slow_loadफ़ंक्शन मिला है, उसे जोड़ देता हूं।
डेविड प्लेनेला
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.