About the Application:
- There is a main application which allows you to choose what set of images you would like to download.
- After the user chooses the set (multiple sets can be chosen at a time), a download window is displayed with progress bar.
- The main application is the root window and the download windows are toplevel windows with parent as root.
- The downloading of set of images uses
multiprocessing.pool.ThreadPool
Problem:
- When there is a single download i.e., only one toplevel window, no problem arises, but if there are simultaneous downloads i.e., multiple toplevel windows, then these windows affect each other.
- By affecting each other I mean, the percentage label gets corrupted and get mixed with other percentage labels, also the actual download process (writing content to file) also gets mixed up.
What I have tried:
- Creating toplevel windows with root as parent e.g.
top = tk.Toplevel(root)
.
Outcome: Same problem as before. - Creating toplevel windows without a parent e.g.
top = tk.Toplevel()
.
Outcome: Same problem as before. - Using
ThreadPoolExecutor
fromconcurrent.futures
withmap()
andsubmit()
methods.
Outcome: Same problem as before. - Removing
ThreadPool
and using a simplefor
loop.
Outcome: The main application freezes until the loop is completed and also, the downloading gets slower as one download at a time.
Code:
# imports done already
# running main application
def start_gui():
root = tk.Tk()
root.wm_iconbitmap('logo.ico')
MainWindow(root)
root.mainloop()
# class for download window
class DownloadBox:
def __init__(self, root, urls, name, download_path):
# creating toplevel window without parent
top = tk.Toplevel()
top.protocol('WM_DELETE_WINDOW', lambda: None)
top.geometry('400x120+{}+{}'.format((root.winfo_rootx() + root.winfo_width() - top.winfo_width()) // 2,
(root.winfo_rooty() + root.winfo_height() - top.winfo_height()) // 2))
top.resizable(0, 0)
top.title(name)
top.wm_iconbitmap('logo.ico')
top.configure(background='#d9d9d9')
# ... adding widgets to window
# progressbar widget
self.style = ttk.Style(top)
self.style.layout('text.Horizontal.TProgressbar',
[('Horizontal.Progressbar.trough',
{'children': [('Horizontal.Progressbar.pbar',
{'side': 'left', 'sticky': 'ns'})],
'sticky': 'nswe'}),
('Horizontal.Progressbar.label', {'sticky': ''})])
self.style.configure('text.Horizontal.TProgressbar', text='0%')
self.progress_bar = ttk.Progressbar(top)
self.progress_bar.place(relx=0.025, rely=0.450, height=20, width=380)
self.progress_bar.configure(orient='horizontal')
self.progress_bar.configure(length=len(urls))
self.progress_bar.configure(mode='determinate')
self.progress_bar.configure(style='text.Horizontal.TProgressbar')
self.progress_bar.configure(value=0)
# ThreadPool implementation
ThreadPool(10).imap_unordered(self.download, self.urls)
# tried this
# for url in self.urls:
# self.download(url)
# progress bar increment function
def increment_progressbar(self):
self.current_value += 1
self.progress_bar['value'] = self.current_value * 100 / len(self.urls)
# this percentage label gets mixed up with other windows
self.style.configure('text.Horizontal.TProgressbar', text='{:0.0%}'.format(self.current_value / len(self.urls)))
if self.current_value == len(self.urls):
self.top.after(1000, self.top.destroy)
# ThreadPool calls this function
def download(self, url):
if not self.cancel_download:
try:
response = requests.get(url, stream=True)
with open(self.download_path + '/' + url.split('/')[-1], 'wb') as f:
for chunk in response:
f.write(chunk)
if self.top.winfo_exists():
self.increment_progressbar()
except:
messagebox.showinfo(title='Connection Timed Out', message='{} Couldn\'t be Downloaded... Check Internet Connection or Try Again Later'.format(self.name))
self.cancel()
# cancel button functionality
def cancel(self):
self.cancel_download = True
self.top.destroy()
# class for main applicaton
class MainWindow:
def __init__(self, top):
top.geometry('810x600+300+80')
top.resizable(0, 0)
top.configure(background='#d9d9d9')
self.top = top
# initializing on startup
self.init_func()
def init_func(self):
# doing somework
# pseudocode for checking for button click
if button.cliked():
self.download(args)
def download(self, args):
urls, name, download_path = args
# passing parent window only to get dimensions
DownloadBox(self.top, urls, name, download_path)
if __name__ == '__main__':
start_gui()
What I expect:
- Make the download windows independent of each other, i.e. the download should process independently and also the percentage labels
- Make use of
ThreadPool
while downloading
Questions:
- Are there any other threading means to download?
- Should I be using
ProcessPool
, if so, how?
Update:
# inside __init__ method of DownloadBox class
self.style.layout('{}.text.Horizontal.TProgressbar'.format(self.name),
[('Horizontal.Progressbar.trough',
{'children': [('Horizontal.Progressbar.pbar',
{'side': 'left', 'sticky': 'ns'})],
'sticky': 'nswe'}),
('Horizontal.Progressbar.label', {'sticky': ''})])
self.style.configure('{}.text.Horizontal.TProgressbar'.format(self.name), text='0%')
# inside increment_progressbar method
# here the variable name is passed as parameter to increment_progressbar method
self.style.configure('{}.text.Horizontal.TProgressbar'.format(name), text='{:0.0%}'.format(self.current_value / len(self.urls)))
Outcome: progress_bar
is working fine... But an issue still exists with ThreadPool