Question: When the download button is pressed it is supposed to download a set of files from a specified url. After each download I update the progress bar accordingly, but the bar only updates after all the files have been downloaded. The UI is being blocked until all downloads are complete. I am using Beeware's Toga for the UI. I have tried using async and await but the download function is synchronous so it doesn't work. Below is the code: """ Downloader """ import pathlib, os import toga from toga.style import Pack from toga.style.pack import COLUMN, ROW, LEFT, CENTER, TOP from downloader.my_libs.downloader import downloader WHITE = '#ffffff' PRIMARY_COLOR = '#000000' SECONDARY_COLOR = '#fbfbfb' ACCENT_COLOR = '#80ff80' BACKGROUND_COLOR = '#ffffff' class Downloader(toga.App): def __init__(self, formal_name=None, app_id=None, app_name=None, id=None, icon=None, author=None, version=None, home_page=None, description=None, startup=None, windows=None, on_exit=None, factory=None): super().__init__(formal_name, app_id, app_name, id, icon, author, version, home_page, description, startup, windows, on_exit, factory) self.resource_folder = pathlib.Path(__file__).joinpath('../resources').resolve() self.url_filename = self.resource_folder.joinpath('url.txt') self.output_path = pathlib.Path(__file__).joinpath('../../../../files').resolve() self.downloader = Downloader(url='', output_path=self.output_path) self.number_of_items_downloaded = 0 self.number_of_items_to_download = 0 def startup(self): main_box = toga.Box(style=Pack(background_color=BACKGROUND_COLOR)) box = toga.Box(style=Pack(background_color=BACKGROUND_COLOR)) url_box = toga.Box() title_label = toga.Label(text='Downloader', style=Pack(text_align=CENTER, font_size=20, flex=1, background_color=BACKGROUND_COLOR, color=PRIMARY_COLOR)) self.url_input = toga.TextInput(style=Pack(flex=4, font_size=12, background_color=SECONDARY_COLOR, color=PRIMARY_COLOR)) self.download_button = toga.Button('Download', on_press=self.on_download, style=Pack(flex=1, font_size=12, padding_top=5, background_color=ACCENT_COLOR, color=PRIMARY_COLOR)) self.info_box = toga.MultilineTextInput(readonly=True, style=Pack(flex=1, padding_top=10, font_size=12, background_color=SECONDARY_COLOR, color=PRIMARY_COLOR)) self.progress_bar = toga.ProgressBar(max=100, value=0, style=Pack(padding_top=10, background_color=SECONDARY_COLOR, color=ACCENT_COLOR)) box.add(title_label) url_box.add(self.url_input) box.add(url_box) box.add(self.download_button) box.add(self.info_box) box.add(self.progress_bar) main_box.add(box) box.style.update(direction=COLUMN, padding=50, flex=1) url_box.style.update(direction=ROW, flex=1, padding_top=5) self.main_window = toga.MainWindow(title=self.formal_name) self.main_window.content = main_box self.main_window.show() def on_download(self, widget): self.progress_bar.start() self.download_files() self.progress_bar.stop() def download_files(self): self.number_of_items_to_download = len(self.downloader.urls) for url in self.downloader.urls: try: self.download_file(url) self.update_progress() except Exception as exception: print('Could not download from:', url) print(exception, '\n\n') self.info_box.value = self.info_box.value + f'\nCould not download from: {url}' + f'exception\n\n' def update_progress(self): self.progress_bar.value = (self.number_of_items_downloaded / self.number_of_items_to_download) * 100 def download_file(self, url): title, filepath = self.downloader.download(url) self.info_box.value = self.info_box.value + f'Downloaded {title} \nTo {filepath}\n\n' self.number_of_items_downloaded += 1 def main(): return Downloader() 

Answer: The main principles to remember in most UI frameworks are:  Don't block the main thread for more than a small fraction of a second. Don't touch UI objects on any thread except the main one.  Here's a solution that follows both of those rules. I haven't tested it, but it should be pretty close:  def on_download(self, widget): # As of Toga 0.4, self.loop is pre-set by Toga itself, # so the next line should be removed. self.loop = asyncio.get_event_loop() threading.Thread(target=self.download_files).start() def download_files(self): self.number_of_items_to_download = len(self.downloader.urls) for url in self.downloader.urls: try: self.download_file(url) except Exception as exception: print('Could not download from:', url) print(exception, '\n\n') # TODO: use call_soon_threadsafe to update the UI # self.info_box.value = self.info_box.value + f'\nCould not download from: {url}' + f'exception\n\n' def update_progress(self, title, filepath): self.info_box.value = self.info_box.value + f'Downloaded {title} \nTo {filepath}\n\n' self.progress_bar.value = (self.number_of_items_downloaded / self.number_of_items_to_download) * 100 def download_file(self, url): title, filepath = self.downloader.download(url) self.number_of_items_downloaded += 1 self.loop.call_soon_threadsafe(self.update_progress, title, filepath)  There's no need to join the thread. If you want to update the UI when the download is complete, just do that with another call to call_soon_threadsafe. Even better, you could use a higher-level API like run_in_executor:  # Must add `async`! async def on_download(self, widget): loop = asyncio.get_event_loop() self.number_of_items_to_download = len(self.downloader.urls) for url in self.downloader.urls: try: title, filepath = await loop.run_in_executor( None, self.downloader.download, url ) self.info_box.value = self.info_box.value + f'Downloaded {title} \nTo {filepath}\n\n' self.number_of_items_downloaded += 1 self.progress_bar.value = (self.number_of_items_downloaded / self.number_of_items_to_download) * 100 except Exception as exception: print('Could not download from:', url) print(exception, '\n\n') self.info_box.value = self.info_box.value + f'\nCould not download from: {url}' + f'exception\n\n'