3

I have a text file with the names parsed with commas that looks like this:

Ann Marie,Smith,ams@companyname.com

The list could have over 100+ names in it. I left out the code that generates all the other GUI components to focus on loading the combobox and the items.

Problem:

How do I implement asyncio to read the text file without blocking the main thread to load the other GUI components.

This is the best I could come up with:

import wx
import asyncio


class Mywin(wx.Frame):

    def __init__(self, parent, title):
        super(Mywin, self).__init__(parent, title=title, size=(300, 200))


        self.panel = wx.Panel(self)
        box = wx.BoxSizer(wx.VERTICAL)
        self.eventloop()

        box.Add(self.combo, 1, wx.EXPAND | wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 5)
        box.AddStretchSpacer()

        self.panel.SetSizer(box)
        self.Centre()
        self.Show()

        #code to display and position GUI components left out


    async def readlist(self):
        filename = 'employees.txt'
        empList = []
        with open(filename) as f_obj:
            for line in f_obj:
                empList.append(line)
        return empList

    async def managecombobox(self, loop):
        task = loop.create_task(self.readlist())
        return_value = await task
        self.combo = wx.ComboBox(self.panel, choices=return_value)

    def eventloop(self):
        event_loop = asyncio.get_event_loop()
        try:
            event_loop.run_until_complete(self.managecombobox(event_loop))
        finally:
            event_loop.close()

    def OnCombo(self, event):
        self.label.SetLabel("You selected" + self.combo.GetValue() + " from Combobox")


app = wx.App()
Mywin(None, 'ComboBox Demo')
app.MainLoop() 
  • Regular files don't support asynchronous operations. This is somewhat OS dependent statement but it least its true for posix AFAIK. However you can emulate this by pushing reading task to a separate thread via `ThreadPoolExecutor`. – freakish Nov 08 '17 at 16:02
  • @freakish - but the read works. It loads the combo box, I just wanted to know if I've done it correctly. –  Nov 08 '17 at 16:03
  • Well, you said `to read the text file without blocking the main thread`. Your code does exactly that: it blocks the main thread until the whole file is read. Other then that its fine. – freakish Nov 08 '17 at 16:03
  • `ThreadPoolExecutor` doesn't block the main thread, correct? what's wrong with the code, why does async block the main thread? –  Nov 08 '17 at 16:05
  • `ThreadPoolExecutor` creates some background workers (in separate threads) and pushes tasks to it. So yeah, combining it with `await loop.run_in_executor()` you can achieve nonblocking file reading. – freakish Nov 08 '17 at 16:05
  • @freakish - Hmm. I have to read more about asyncio, I though it doesn't block the main thread. Asyncio doesn't create another thread right? The event loop decides when to do it what task, and blocks when it does it, right? –  Nov 08 '17 at 16:07
  • Your functions dont become nonblocking simply because you put `async` keyword in front of them. In order for them to be truely nonblocking you have to use something that is implemented to be nonblocking under the hood (e.g. epoll syscalls). Like all the stuff in `asyncio` module. It is perfectly valid to do a blocking operation in `async` function (the thing you did with `open` and looping over the file object). But of course in that situation you lose benefits of both and get problems of both. – freakish Nov 08 '17 at 16:08
  • @freakish - If I use `threadpoolexecutor`, it won't block correct? –  Nov 08 '17 at 16:10
  • Yes, that's correct. – freakish Nov 08 '17 at 16:10
  • @freakish - Thanks for help, guess I have to go read up more on threading. –  Nov 08 '17 at 16:11
  • 1
    Note that this is **only** for file reading which is always blocking. It's a limitation. I'll give you some code example when I have more time. – freakish Nov 08 '17 at 16:12

1 Answers1

1

This function

async def readlist(self):
    filename = 'employees.txt'
    empList = []
    with open(filename) as f_obj:
        for line in f_obj:
            empList.append(line)
    return empList

is not asynchronous. The file reading is synchronous and no other task can run while this happens. Now since file reading is blocking by default it is not easy to make it asynchronous. One way is to submit the task to a separate thread:

import asyncio
from concurrent.futures import ThreadPoolExecutor

FileIOPool = ThreadPoolExecutor(8)  # you may pass here something like 2*CPU_CORES

class Mywin(wx.Frame):
    ...
    def read_file(self):
        filename = 'employees.txt'
        empList = []
        with open(filename) as f_obj:
            for line in f_obj:
                empList.append(line)
        return empList

    async def readlist(self):
        loop = asyncio.get_event_loop()
        return await loop.run_in_executor(FileIOPool, self.read_file)

Now readlist is truely asynchronous, other operations can run while the file is being read. This is a standard procedure when you want to apply some blocking task to asynchronous framework.

freakish
  • 54,167
  • 9
  • 132
  • 169
  • Thanks for your assistance. Just out of curiosity, how did you learn threading/asyncio? Textbooks, tutorials, school? –  Nov 09 '17 at 02:45
  • I don't need to have `event_loop.run_until_complete(readlist)`? –  Nov 09 '17 at 02:52
  • @S.R. you definitely need it (not necessarily inside a class). Ive just implemented the relevant part. Ive learned asyncio via google, docs and "learn by doing". – freakish Nov 09 '17 at 07:14
  • If I have to use `event_loop.run_until_complete(readlist)`, doesn't it mean I'm blocking until that thread finishes? –  Nov 09 '17 at 12:42
  • I asked a [question](https://stackoverflow.com/questions/54282226/trying-to-undestand-why-creating-and-manipulating-futures-a-bad-practice?noredirect=1&lq=1) about async and futures, but no one has answered, could you possibly help? –  Jan 21 '19 at 10:40