পাইথন জিওবজেক্ট ইন্ট্রোস্পেকশন অ্যাপগুলিতে কীভাবে অ্যাসিক্রোনাস কাজগুলি চালানো যায়


16

আমি একটি পাইথন + গবজেক্ট অ্যাপ লিখছি যা শুরুতে ডিস্ক থেকে অ-তুচ্ছ পরিমাণে ডেটা পড়তে হবে। ডেটা সিঙ্ক্রোনাসলি পড়া হয় এবং রিড অপারেশন শেষ করতে 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()

আমি এই ধরণের জিনিসটির জন্য অতীতে জিটিস্ককে ব্যবহার করেছি , তবে আমি উদ্বিগ্ন যে এর কোডটি 3 বছরে স্পর্শ করা হয়নি, কেবলমাত্র গবজেক্ট ইন্ট্রোস্পেকশনে পোর্ট করা উচিত। সর্বাধিক গুরুত্বপূর্ণ, এটি উবুন্টু 12.04 এ আর উপলব্ধ নেই। সুতরাং আমি কার্যকরী পাইথন উপায়ে বা একটি GObject / GTK + স্ট্যান্ডার্ড পদ্ধতিতে অ্যাসিঙ্ক্রোনালিভাবে কাজগুলি চালনার সহজ উপায়টি খুঁজছি।

সম্পাদনা করুন: আমি যা করার চেষ্টা করছি তার একটি উদাহরণ সহ এখানে কিছু কোড। আমি python-deferমন্তব্যে প্রস্তাবিত হিসাবে চেষ্টা করেছি , তবে আমি দীর্ঘমেয়াদী কাজটি অবিচ্ছিন্নভাবে চালানোর ব্যবস্থা করতে পারি না এবং এটি শেষ হওয়ার জন্য অপেক্ষা না করেই ইউআই লোড হতে দেয়। পরীক্ষার কোডটি ব্রাউজ করুন

অ্যাসিক্রোনাস টাস্কগুলি চালনার কোনও সহজ এবং বহুল ব্যবহৃত উপায় এবং সেগুলি শেষ হয়ে গেলে কী বিজ্ঞপ্তি পাওয়া যায়?


এটি খুব সুন্দর উদাহরণ নয়, তবে আমি নিশ্চিত যে আপনি যা খুঁজছেন তা হ'ল
রোবটহুমানস

দুর্দান্ত, আমি মনে করি আপনার async_callফাংশনটি আমার যা প্রয়োজন তা হতে পারে। আপনি কি এতে কিছুটা প্রসারিত করতে এবং একটি উত্তর যুক্ত করতে আপত্তি করবেন, যাতে আমি এটি গ্রহণ করতে পারি এবং এটি পরীক্ষা করার পরে আপনাকে জমা দিতে পারি? ধন্যবাদ!
ডেভিড প্ল্যানেলা

1
দুর্দান্ত প্রশ্ন, খুব দরকারী! ;-)
রাফা সিলেক

উত্তর:


15

আপনার সমস্যাটি খুব সাধারণ একটি সমস্যা, তাই এখানে প্রচুর সমাধান রয়েছে (শেড, মাল্টিপ্রসেসিং বা থ্রেডিং সহ সারি, কর্মী পুল, ...)

এটি যেহেতু সাধারণ, তাই পাইথন বিল্ট-ইন সলিউশনও রয়েছে (৩.২-এ, তবে এখানে ব্যাকপোর্ট করা হয়েছে: 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. আপনি যে লাইব্রেরিটি (সফ্টওয়্যারসেন্টার.ডিবি) কল করেছেন সেটি থ্রেডসেফ নয় (এটি জিটিকে বা অনুরূপ অন্তর্ভুক্ত বলে মনে হচ্ছে) অতএব, এই পদ্ধতিগুলিকে থ্রেডে কল করার ফলে অদ্ভুত আচরণের ফলাফল হয় (আমার পরীক্ষায়, 'কোর ডাম্প' ওভার থেকে 'সব কিছু কার্যকর হয়) থেকে শুরু করে ফলাফল ছাড়াই ছেড়ে দেওয়া)। সুতরাং: সফ্টওয়্যারসেন্টার সহ কোনও থ্রেড নেই।
  4. জিটিকে-তে প্রতিটি অ্যাসিনক্রোনাস কলব্যাক কলব্যাকের চালা ছাড়া কিছু করা উচিত নয় যা গ্লিব মেইনলুপে ডাকা হবে। সুতরাং: না print, কোনও কলব্যাক যোগ করা ছাড়া কোনও জিটিকে রাষ্ট্র পরিবর্তন হয় না!
  5. Gtk এবং একইভাবে বাক্সের বাইরে থ্রেড নিয়ে কাজ করে না। আপনাকে যা করতে হবে threads_init, এবং যদি আপনি একটি GTK বা একইভাবে পদ্ধতি কল, আপনি যে পদ্ধতি (পূর্ববর্তী সংস্করণে এই ছিল রক্ষা আছে gtk.gdk.threads_enter(), gtk.gdk.threads_leave()উদাহরণস্বরূপ GStreamer জন্য দেখুন:। Http://pygstdocs.berlios.de/pygst-tutorial/playbin। এইচটিএমএল )।

আমি আপনাকে নিম্নলিখিত পরামর্শটি দিতে পারি:

  1. আপনার slow_loadপছন্দসই ফলাফলগুলি ফিরিয়ে দিতে পুনরায় লিখুন এবং প্রক্রিয়াগুলি সহ ফিউচারগুলি ব্যবহার করুন।
  2. সফ্টওয়্যারসেন্টার থেকে পাইথন-অ্যাপ্ট বা অনুরূপে স্যুইচ করুন (আপনি সম্ভবত এটি পছন্দ করেন না)। তবে আপনার ক্যানোনিকাল দ্বারা নিযুক্ত হওয়ার পরে, আপনি সফ্টওয়্যারসেন্টার বিকাশকারীদের সরাসরি তাদের সফ্টওয়্যারটিতে ডকুমেন্টমেন্ট যুক্ত করতে বলতে পারেন (উদাহরণস্বরূপ যে এটি থ্রেড নিরাপদ নয়) এবং আরও ভাল করে, সফ্টওয়্যার সেন্টার থ্রেডসেফ তৈরি করে।

একটি নোট হিসাবে: সমাধান অন্যদের দ্বারা প্রদত্ত ( Gio.io_scheduler_push_job, async_call) না দিয়ে কাজ time.sleepকিন্তু সঙ্গে softwarecenter.db। এটি, কারণ এটি সমস্ত থ্রেড বা প্রক্রিয়া এবং থ্রেডগুলিতে gtk এবং এর সাথে কাজ না করার জন্য ফোটায় softwarecenter


ধন্যবাদ! আমি আপনার উত্তরটি গ্রহণ করতে যাচ্ছি কারণ এটি কেন আমাকে অযোগ্য হতে পারে তার খুব বিশদে আমাকে নির্দেশ করে। দুর্ভাগ্যক্রমে, আমি আমার অ্যাপ্লিকেশনটিতে উবুন্টু ১২.০৪ এর জন্য প্যাকেজযুক্ত সফ্টওয়্যারটি ব্যবহার করতে পারি না (এটি কোয়ান্টালের জন্য, যদিও লঞ্চপ্যাড.এন.বিউন্টু /+ সোর্স / প্যাথন-কনকন্টার.ফিউচার ) তবে আমি অনুমান করি যে আমি সক্ষম না হয়ে আটকেছি আমার টাস্কটি অবিচ্ছিন্নভাবে চালানোর জন্য। সফ্টওয়্যার সেন্টার বিকাশকারীদের সাথে কথা বলার জন্য নোটটি সম্পর্কে, আমি কোড এবং ডকুমেন্টেশনে পরিবর্তনগুলি অবদান রাখতে বা তাদের সাথে কথা বলার জন্য যে কোনও স্বেচ্ছাসেবীর মতো একই অবস্থানে
রয়েছি

জিআইএল আইও চলাকালীন প্রকাশিত হয় তাই থ্রেডগুলি ব্যবহার করা পুরোপুরি ঠিক। যদিও অ্যাসিঙ্ক আইও ব্যবহার করা হয় তবে এটি প্রয়োজনীয় নয়।
jfs

10

জিআইও-র আই / ও সিডিউলার ব্যবহার করে এখানে আরও একটি বিকল্প রয়েছে (পাইথন থেকে আমি এর আগে কখনও ব্যবহার করি নি, তবে নীচের উদাহরণটি ভাল চলছে বলে মনে হচ্ছে)।

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 () এছাড়াও দেখুন, আপনি যদি একবার মূল থ্রেডে চালাতে চান তবে একবার ধীরে ধীরে কাজ শেষ হয়।
সিগফ্রিড গেভ্যাটার

উত্তর এবং উদাহরণের জন্য ধন্যবাদ সিগফ্রাইড। দুর্ভাগ্যক্রমে, দেখে মনে হচ্ছে যে আমার বর্তমান কাজটি সহ আমার কাছে জিও এপিআই ব্যবহার করার সুযোগ নেই এটি অযৌক্তিকভাবে চালিত করার জন্য।
ডেভিড প্লানেলা

এই সত্যিই দরকারী ছিল, কিন্তু যতদূর আমি বলতে পারি Gio.io_scheduler_job_send_to_mainloop পাইথন মধ্যে :( বিদ্যমান নয় হিসাবে
Sil

2

আপনি GLib.idle_add (কলব্যাক) ব্যবহার করে দীর্ঘ চলমান কার্যটি কল করতে একবার GLib মেনলুপ এর উচ্চ অগ্রাধিকারের সমস্ত ঘটনা শেষ করে (যা আমি বিশ্বাস করি যে ইউআই নির্মাণের অন্তর্ভুক্ত রয়েছে)।


ধন্যবাদ মাইক হ্যাঁ, ইউআই প্রস্তুত হওয়ার পরে এটি অবশ্যই কাজটি শুরু করতে সহায়তা করবে। কিন্তু অন্যদিকে, আমি বুঝতে পারি যে যখন callbackডাকা হয়, তখন এটি সিঙ্ক্রোনসিভভাবে সম্পন্ন হবে, এভাবে ইউআইকে অবরুদ্ধ করে দেবে, তাই না?
ডেভিড প্লানেলা

নিষ্ক্রিয়_এডডি তেমন কাজ করে না। একটি নিষ্ক্রিয়_এডডিতে ব্লক করা কল করা এখনও খারাপ কাজ, এবং এটি ইউআই-তে আপডেটগুলি হওয়া থেকে বিরত রাখবে। এবং এমনকি অ্যাসিঙ্ক্রোনাস এপিআই এখনও অবরুদ্ধ হতে পারে, যেখানে ইউআই এবং অন্যান্য কার্যগুলি অবরুদ্ধ করার একমাত্র উপায় হ'ল এটি ব্যাকগ্রাউন্ড থ্রেডে করা।
dobey

আদর্শভাবে আপনি আপনার ধীর কাজটি খণ্ডগুলিতে ভাগ করে নিতে পারেন, যাতে আপনি এটির একটি অলস কলব্যাকের মধ্যে কিছুটা চালাতে পারেন, ফিরে (এবং ইউআই কলব্যাকের মতো অন্যান্য জিনিস চালাতে দিন), আপনার কলব্যাকটি আবার কল করার পরে আরও কিছু কাজ চালিয়ে যেতে পারেন, এবং তাই চালু.
সিগফ্রিড গেভ্যাটার

একটি গ্যাচা idle_addহ'ল কলব্যাকের রিটার্ন মানটি গুরুত্বপূর্ণ। এটি সত্য হলে এটি আবার ডাকা হবে।
ফ্লিম

2

Introspected ব্যবহার করুন Gioএপিআই একটি ফাইল পড়তে, তার অ্যাসিঙ্ক্রোনাস পদ্ধতি, এবং যখন প্রাথমিক কল করার সঙ্গে একটি সময়পর্ব হিসাবে এটা করতে GLib.timeout_add_seconds(3, call_the_gio_stuff)যেখানে call_the_gio_stuffএকটি ফাংশন যা রিটার্ন হয় False

এখানে সময়সীমাটি যুক্ত করা প্রয়োজন (যদিও বিভিন্ন সেকেন্ডের বিভিন্ন সংখ্যার প্রয়োজন হতে পারে), যদিও জিও অ্যাসিঙ্ক কলগুলি অ্যাসিঙ্ক্রোনাস হলেও সেগুলি অবরুদ্ধ নয়, মানে বড় ফাইল পড়ার ভারি ডিস্ক ক্রিয়াকলাপ বা বড় ফাইলের সংখ্যা, ইউআই এবং আই / ও এখনও একই (মূল) থ্রেডে থাকায় অবরুদ্ধ ইউআই-এর ফলে তৈরি হতে পারে।

আপনি যদি পাইথনের ফাইল আই / ও এপিআই ব্যবহার করে আপনার নিজের ফাংশনগুলি অ্যাসিঙ্ক হওয়ার জন্য এবং প্রধান লুপের সাথে একীভূত করতে চান তবে আপনাকে কোডটি একটি গওজেক্ট হিসাবে লিখতে হবে, বা চারপাশে কলব্যাকগুলি পাস করতে হবে, বা python-deferআপনাকে সহায়তা করতে ব্যবহার করতে হবে এটা কর. তবে এখানে জিও ব্যবহার করা ভাল, কারণ এটি আপনাকে প্রচুর সুন্দর বৈশিষ্ট্য বয়ে আনতে পারে, বিশেষত আপনি যদি ইউএক্স-এ ফাইল ওপেন / সেভ স্টাফ করছেন।


ধন্যবাদ @ ডাবি আমি আসলে ডিস্ক থেকে সরাসরি কোনও ফাইল পড়ছি না, সম্ভবত আমার মূল পোস্টে আরও পরিষ্কার করা উচিত ছিল। দীর্ঘমেয়াদী যে কাজটি আমি চালাচ্ছি তা হ'ল জিজ্ঞাসাবাবু / প্রশ্নস / ১৩৯৯২২ / এর উত্তর অনুসারে সফটওয়্যার সেন্টার ডাটাবেস পড়ছে , তাই আমি নিশ্চিত যে আমি Gioএপিআই ব্যবহার করতে পারব না । আমি যা ভাবছিলাম তা হ'ল জি.টি.এসক যেভাবে জেনারিক দীর্ঘ-চলমান কোনও কাজকে অবিচ্ছিন্নভাবে চালানোর কোনও উপায় আছে কিনা।
ডেভিড প্ল্যানেলা

আমি জানি না জিটি টাস্কটি আসলে কী, তবে আপনি যদি gtask.sourceforge.net বলতে চান তবে আপনার মনে হয় না যে এটি ব্যবহার করা উচিত। যদি এটি অন্য কিছু হয় তবে আমি জানি না এটি কী। তবে মনে হচ্ছে যে আমি আপনাকে উল্লিখিত দ্বিতীয় রুটটি গ্রহণ করতে হবে এবং সেই কোডটি মোড়ানোর জন্য কিছু অ্যাসিঙ্ক্রোনাস এপিআই প্রয়োগ করতে হবে, বা কেবল এটি সমস্ত থ্রেডে করতে হবে।
dobey

প্রশ্নের একটি লিঙ্ক আছে। জিটিস্ক হ'ল: ছিলেন: chergert.github.com/gtask
ডেভিড প্ল্যানেলা

1
আহ, এটিকে পাইথন-ডিফার (এবং মোচড়িত স্থগিত করা API) সরবরাহিত API এর সাথে খুব মিল রয়েছে। সম্ভবত আপনি পাইথন-ডিফার ব্যবহার করা উচিত?
dobey

1
উদাহরণস্বরূপ GLib.idle_add () ব্যবহার করে প্রধান অগ্রাধিকারের ইভেন্টগুলি না হওয়া পর্যন্ত আপনাকে এখনও ডেকে আনা দরকার। ভালো লেগেছে এই: pastebin.ubuntu.com/1011660
dobey

1

আমি মনে করি এটি লক্ষ্য করা যায় যে @ মল যা বলেছিল তা করার এটি একটি বিভক্ত উপায় way

মূলত, আপনি একটি রান পেয়েছেন তারপর 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()

অতিরিক্ত দ্রষ্টব্য, আপনার অন্য থ্রেডটি সঠিকভাবে শেষ হওয়ার আগে বা ফাইলের জন্য যাচাই করার আগে আপনাকে শেষ করতে হবে your আপনার সন্তানের থ্রেডে লক করুন।

মন্তব্যে ঠিকানা সম্পাদনা করুন:
প্রথমদিকে আমি ভুলে গিয়েছিলাম GObject.threads_init()। স্পষ্টতই যখন বোতামটি চালিত হয়েছিল, এটি আমার জন্য থ্রেডিং সূচনা করেছিল। এটি আমার জন্য ভুলটি মুখোশ করেছে।

সাধারণত প্রবাহটি স্মৃতিতে উইন্ডো তৈরি করে, তাত্ক্ষণিকভাবে অন্য থ্রেডটি চালু করে, যখন থ্রেডটি বোতামটির আপডেট সম্পূর্ণ করে। উইন্ডোটি আঁকানোর আগে পুরো আপডেটটি চালানো উচিত কিনা তা যাচাই করতে আমি Gtk.main কল করার আগে আমি একটি অতিরিক্ত ঘুম যোগ করেছি। থ্রেড লঞ্চটি উইন্ডো অঙ্কন মোটেই বাধা দেয় না তা যাচাই করার জন্য আমি এটির মন্তব্যও করেছি।


1
ধন্যবাদ। আমি নিশ্চিত না যে আমি এটি অনুসরণ করতে পারি। একটির জন্য, আমি slow_loadইউআইটি শুরু হওয়ার খুব শীঘ্রই মৃত্যুদন্ড কার্যকর করার প্রত্যাশা করতাম , তবে বোতামটি ক্লিক না করা ছাড়া এটি কখনই ডাকা হবে বলে মনে হয় না, যা আমাকে কিছুটা বিভ্রান্ত করে, যেমন আমি ভেবেছিলাম বোতামটির উদ্দেশ্য কেবলমাত্র ভিজ্যুয়াল ইঙ্গিত প্রদান করা ছিল টাস্কের অবস্থা।
ডেভিড প্লানেলা

দুঃখিত, আমি একটি লাইন মিস করেছি। এটা এটা করেছে। আমি গবজেক্টকে থ্রেডগুলির জন্য প্রস্তুত হতে ভুলে গেছি।
রোবটহুমানস

তবে আপনি কোনও থ্রেড থেকে মূল লুপটিতে কল করছেন যা সমস্যার কারণ হতে পারে, যদিও এগুলি সহজেই আপনার তুচ্ছ উদাহরণে প্রকাশিত হয় না যা কোনও আসল কাজ করে না।
dobey

বৈধ পয়েন্ট, তবে আমি মনে করি না যে একটি তুচ্ছ উদাহরণটি ডিবিবাসের মাধ্যমে বিজ্ঞপ্তি প্রেরণের যোগ্যতাযুক্ত (যা আমি মনে করি যে একটি তুচ্ছ অ্যাপটি করা উচিত)
রোবটহুমানস 27:55

এইচ এম, async_callএই উদাহরণটি চালানো আমার পক্ষে কাজ করে তবে এটি আমার অ্যাপ্লিকেশনটিতে পোর্ট করার সময় তা মায়াম এনে দেয় এবং আমি যে আসল slow_loadফাংশনটি পেয়েছি তা যুক্ত করি।
ডেভিড প্লানেলা
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.