Python: Correct way to not block a GTK UI while doing HTTP requests

646 Views Asked by At

Question: What is the right way to not block a GTK UI while loading data from a remote HTTP source?

Background:

I'm working on a cryptocurrency price indicator for Ubuntu Unity. I've modified the original project so that it can run multiple tickers at the same time.

Schematically, it works like this:

The main thread finds every ticker to be loaded and spins up a GTK libAppindicator instance (Indicator) for each. Each indicator then loads a class that retrieves ticker data from the remote API and feeds it back to the indicator.

However, I noticed that the UI becomes irresponsive at irregular intervals. I assume that this is because the HTTP requests (with the requests library) are blocking. So I decided to start every Indicator in its own thread, but the same issue still occurs once the tickers are all loaded. (I don't understand why, if something is on its own thread, it should not block the main thread?)

I've then tried to replace requests with grequests, however I can't get this to work as the callback seems to never get called. There also seems to be something like promises, but the documentation for all of this looks outdated and incomplete.

Code:

# necessary imports here

class Coin(Object):
    self.start_main()
    self.instances = []

    def start_main(self):
        self.main_item = AppIndicator.Indicator.new(name, icon)
        self.main_item.set_menu(self._menu()) # loads menu and related actions (code omitted)

    def add_indicator(self, settings=None):
        indicator = Indicator(self, len(self.instances), self.config, settings)
        self.instances.append(indicator)
        nt = threading.Thread(target=indicator.start())
        nt.start()

    def _add_ticker(self, widget): # this is clickable from the menu (code omitted)
        self.add_indicator('DEFAULTS')

class Indicator():
    instances = []

    def __init__(self, coin, counter, config, settings=None):
        Indicator.instances.append(self)

    def start(self):
        self.indicator = AppIndicator.Indicator.new(name + "_" + str(len(self.instances)), icon)
        self.indicator.set_menu(self._menu())
        self._start_exchange()

    def set_data(self, label, bid, high, low, ask, volume=None):
        # sets data in GUI

    def _start_exchange(self):
        exchange = Kraken(self.config, self)
        exchange.check_price()
        exchange.start()

class Kraken:
  def __init__(self, config, indicator):
    self.indicator = indicator
    self.timeout_id = 0

  def start(self):
    self.timeout_id = GLib.timeout_add_seconds(refresh, self.check_price)

  def stop(self):
    if self.timeout_id:
      GLib.source_remove(self.timeout_id)

  def check_price(self):
    res = requests.get('https://api.kraken.com/0/public/Ticker?pair=XXBTZUSD')
    data = res.json()
    self._parse_result(data['result'])

  def _parse_result(self, data):
    # code to parse json result
    self.indicator.set_data(label, bid, high, low, ask)

coin = Coin()
Gtk.main()
0

There are 0 best solutions below