Toplevel windows влияет на друг друга при использовании с ThreadPool в tkinter - PullRequest
0 голосов
/ 25 апреля 2020

О приложении:

  • Существует основное приложение, которое позволяет вам выбрать, какой набор изображений вы хотите загрузить.
  • После того, как пользователь выбирает набор (можно выбрать несколько наборов одновременно), отображается окно загрузки с индикатором выполнения.
  • Основным приложением является root окно и загружаемые файлы windows toplevel windows с родителем как root.
  • При загрузке набора изображений используется multiprocessing.pool.ThreadPool

Проблема:

  • Когда есть одна загрузка, т.е. только одно окно верхнего уровня, никаких проблем не возникает, но если есть одновременные загрузки, то есть несколько уровней верхнего уровня windows, то эти windows влияют друг на друга.
  • Под влиянием друг на друга я имею в виду, что процентная метка повреждена и смешана с другими процентными метками, а также сам процесс загрузки (запись содержимого в файл) также перепутан.

Что я пробовал:

  • Создание верхнего уровня windows с root в качестве родителя, например top = tk.Toplevel(root).
    Результат : Та же проблема, что и раньше.
  • Создание верхнего уровня windows без родителя, например top = tk.Toplevel().
    Результат: Та же проблема, что и раньше.
  • Использование ThreadPoolExecutor из concurrent.futures с методами map() и submit().
    Результат: Та же проблема, что и раньше.
  • Удаление ThreadPool и использование простого for l oop.
    Результат: Основное приложение зависает до тех пор, пока не завершится l oop, а также загрузка замедляется при загрузке по одной за раз.

Код:

# 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()

Что я ожидаю:

  • Сделайте загрузку windows Inde зависит друг от друга, то есть загрузка должна обрабатываться независимо, а также процентные метки
  • Использовать ThreadPool при загрузке

Вопросы:

  • Существуют ли какие-либо другие средства для загрузки?
  • Должен ли я использовать ProcessPool, если да, то как?

Обновление:

# 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)))

Результат: progress_bar работает нормально ... Но проблема все еще существует с ThreadPool

...