Есть ли способ остановить или отменить получение URL-адреса в python?

Итак, я в основном написал программу на python, используя tkinter и urllib.request, которая должна работать как загрузчик, но у каждого загрузчика должна быть кнопка паузы или отмены, но я не могу найти хоть бы это сделать! Недавно я столкнулся с тем же вопросом в stackoverflow (ссылка: Возможно ли остановить (отменить) процесс получения URL-адресов?), и мне кажется, что я должен использовать потоки или многопроцессорную обработку, но я понятия не имею, как это сделать! Кстати, как многопоточность или многопроцессорность помогут с отменой приостановки загрузки? Может кто-нибудь объяснить мне, что мне делать? Есть ли способ сделать это без потоковой или многопроцессорной обработки? Если нет, можете ли вы объяснить, как использовать многопоточность или многопроцессорность в этой программе, потому что я понятия не имею, что делать! Пожалуйста, помогите мне в этом. Мой код:

from tkinter import *
from tkinter import font as tkFont
import random
import urllib.request
import requests


def printsth():
    print("Yay it works! ")


def main_menu():
    root = Tk()
    root.title('8-bit downloader ')
    root.iconbitmap(r"C:\Users\rayanravesh\PycharmProjects\GUI_Calculator\icon.ico")
    root.geometry("600x280")
    # the top menu
    num = IntVar()
    chum = IntVar()
    # var = IntVar()
    menu = Menu(root)
    root.config(menu=menu)
    submenu = Menu(menu)
    menu.add_cascade(label="Settings", menu=submenu)

    def custom_op():
        custom = Toplevel()
        custom.iconbitmap(r"C:\Users\rayanravesh\PycharmProjects\GUI_Calculator\icon.ico")
    submenu.add_command(label="Customization ", command=custom_op)

    def settings_op():
        global gps
        set_win = Toplevel()
        set_win.iconbitmap(r"C:\Users\rayanravesh\PycharmProjects\GUI_Calculator\icon.ico")
        path_label = Label(set_win, text="Current default download path: ")
        path_entry = Entry(set_win, width=30)
        file_read = open('Data.txt', 'r')
        data_base = file_read.read()
        path_entry.insert(0, data_base)
        file_read.close()

        def default_output():
            global location
            file_read2 = open('Data.txt', 'r+')
            file_read2.truncate(0)
            file_read2.close()
            write_file2 = open('Data.txt', 'w')
            write_file2.write(path_entry.get())
            write_file2.close()
            location = path_entry.get() + "\\"
            default_location = location.replace("\\", "\\\\")
        path_btn = Button(set_win, text="Submit ", command=default_output)
        path_label.pack(anchor=CENTER, expand=1)
        path_entry.pack(anchor=CENTER, expand=1)
        path_btn.pack(anchor=CENTER, expand=1)
    submenu.add_command(label="Settings ", command=settings_op)
    submenu.add_separator()
    submenu.add_command(label="Exit", command=root.destroy)

    # the section menu
    editmenu = Menu(menu)
    menu.add_cascade(label="Sections(soon)", menu=editmenu)
    editmenu.add_command(label="Downloader", command=printsth)
    editmenu.add_command(label="Converter", command=printsth)
    editmenu.add_command(label="Media Player", command=printsth)
    editmenu.add_command(label="Editor", command=printsth)
    # the tool bar
    toolbar = Frame(root, bg="light gray")
    insert_button = Button(toolbar, text="Insert an image", command=printsth)
    insert_button.pack(side=LEFT, padx=2, pady=2)
    print_button = Button(toolbar, text="Print", command=printsth)
    print_button.pack(side=LEFT, padx=2, pady=2)
    toolbar.pack(side=TOP, fill=X)

    # the download function
    def download_image():
        global formatname
        if num.get() == 1:
            name = random.randrange(1, 1000000)
        else:
            name = str(name_entry.get())
        formatname = str(format_entry.get())
        '''if var.get() == 1:
            operator = str(url_entry.get())
            formatname = '.' + operator[-3] + operator[-2] + operator[-1]
        else:
            pass'''
        fullname = str(name) + formatname
        url = str(url_entry.get())
        fw = open('file-size.txt', 'w')
        file_size = int(requests.head(url, headers={'accept-encoding': ''}).headers['Content-Length'])
        fw.write(str(file_size))
        fw.close()
        if chum.get() == 1:
            filee = open('Data.txt', 'r')
            destination = filee.read()
            path = destination
            output_entry.insert(0, destination)
            filee.close()
        else:
            output_entry.delete(0, END)
            path = str(output_entry.get()) + "\\"
        urllib.request.urlretrieve(url, path.replace("\\", "\\\\") + fullname)

    # the status bar
    status_bar = Label(root, text="Downloading...", bd=1, relief=SUNKEN, anchor=W)
    status_bar.pack(side=BOTTOM, fill=X)

    # the download frame
    body_frame = Frame(root, bg="light blue")
    download_button = Button(body_frame, text="Download! ", command=download_image, border=3, width=20, height=5)
    download_design = tkFont.Font(size=12, slant='italic')
    download_button['font'] = download_design
    download_button.pack(side=LEFT, pady=5, padx=5)
    body_frame.pack(side=LEFT, fill=Y)
    # the main interaction menu
    inter_frame = Frame(root)
    url_entry = Entry(inter_frame, width=30)
    label = Label(inter_frame, text="Enter the image URL: ")
    file_format = Label(inter_frame, text="Choose your file format: ")
    format_entry = Entry(inter_frame, width=30)
    file_name = Label(inter_frame, text="File's name: ")
    name_entry = Entry(inter_frame, width=30)
    check_name = Checkbutton(inter_frame, text="Give a random name", variable=num)
    # check_format = Checkbutton(inter_frame, text="Download with default format", variable=var)
    check_default = Checkbutton(inter_frame, text="Download to default path", variable=chum)
    output_path = Label(inter_frame, text="Choose output path: ")
    output_entry = Entry(inter_frame, width=30)
    file_name.pack(anchor=CENTER, expand=1)
    name_entry.pack(anchor=CENTER, expand=1)
    check_name.pack(anchor=CENTER, expand=1)
    label.pack(anchor=CENTER, expand=1)
    url_entry.pack(anchor=CENTER, expand=1)
    file_format.pack(anchor=CENTER, expand=1)
    format_entry.pack(anchor=CENTER, expand=1)
    format_entry.insert(0, '.')
    # check_format.pack(anchor=CENTER)
    output_path.pack(anchor=CENTER, expand=1)
    output_entry.pack(anchor=CENTER, expand=1)
    check_default.pack(anchor=CENTER, expand=1)
    inter_frame.pack(expand=1)
    root.mainloop()

    # the end!


main_menu()

person Omid Ki    schedule 18.04.2020    source источник


Ответы (1)


При запуске такой функции, как urlretrieve, в одном потоке или процессе, остановить ее невозможно, поскольку существует только один поток или процесс.

В этом случае вы звоните urlretrieve из tkinter обратного вызова. Они вызываются tkinter из mainloop, эффективно прерывая mainloop.

Это может не быть проблемой для небольшой загрузки с хоста при быстром соединении. Но если загрузка занимает секунды или минуты, у вас проблемы. Потому что, пока выполняется urlretrieve, ваш графический интерфейс не отвечает, потому что mainloop ожидает завершения вашего обратного вызова.

Таким образом, даже если бы у вас была кнопка "Отмена", она не реагировала бы до тех пор, пока работает urlretrieve.

Прочтите функцию urlretrieve из файла urllib/request.py в вашем каталоге Python. Это не большая функция, и за ней должно быть относительно легко следовать. Внутренне urlretrieve содержит цикл, который reads из URL-адреса и записывает в файл. По умолчанию такое чтение ждать, пока не появится что-нибудь для чтения. Дело в том; нет никакого способа прервать этот цикл.

Так или иначе, вам придется переписать urlretrieve. В этой переписанной версии вы должны проверять каждую итерацию внутреннего цикла, следует ли продолжать.

В основном у вас есть два варианта;

  1. Включите функциональность urlretrieve в цикл обработки событий.
  2. Работайте над функциональностью urlretrieve в другом потоке.

У каждого есть свои плюсы и минусы. Если вы используете Python 3, запустить threading.Thread, вероятно, будет проще всего, потому что tkinter в Python 3 является потокобезопасным.

Пример первого подхода см. в разделе unlock-excel.pyw. из моего репозитория скриптов на github. В этом приложении длинная операция разбита на несколько небольших шагов, которые вызываются из цикла событий tkinter через метод after.

У меня нет удобного примера для метода с использованием потока. По сути, вам нужно переписать urlretrieve, чтобы проверить переменную (например, threading.Event), которая сигнализирует, следует ли останавливаться во внутреннем цикле while.

person Roland Smith    schedule 18.04.2020
comment
потому что tkinter в Python 3 является потокобезопасным: у вас есть ссылка на это? - person stovfl; 18.04.2020
comment
@stovfl См. docs.python.org/3/library/tk.html . Цитата: Кроме того, внутренний модуль _tkinter предоставляет потокобезопасный механизм, который позволяет Python и Tcl взаимодействовать. - person Roland Smith; 18.04.2020
comment
@stovfl Как оказалось, это не безоговорочно верно, если посмотреть на различные актуальные вопросы, поднимавшиеся на протяжении многих лет. Реальность кажется более сложной, чем бинарное да/нет. Здесь в stackoverflow есть примеры, где функции tkinter, вызываемые из разных потоков, работают нормально. - person Roland Smith; 18.04.2020
comment
В _tkinter.c я нахожу ситуацию с потоками это сложно. Tcl не является потокобезопасным, за исключением случаев, когда он настроен с параметром --enable-threads. ... Возможен вызов команд из других потоков; _tkinter поставит в очередь событие для потока интерпретатора - person stovfl; 18.04.2020
comment
@stovfl Я тоже это нашел. На моей платформе по умолчанию создается многопоточный tkinter. - person Roland Smith; 19.04.2020