将线程计时,然后将时间传递给主线程中的另一个函数。

huangapple go评论63阅读模式
英文:

Time a thread, then pass the time to another function in the main thread

问题

我正在编写一个Tkinter应用程序,需要一个不确定进度条,显示在主窗口上方,同时,在后台,调用的函数执行一些操作。因为我不知道这个操作需要多长时间,所以不想计时进度条。此外,被调用的函数将显示一个信息窗口,所以进度条应该停止。我已经查找了多种方法来实现它,我认为我找到了正确的方法。我想让函数在单独的线程中运行,测量线程执行时间,然后以某种方式将时间传递给进度条以停止它。我不知道这是否是正确的方法。另外,它不起作用,主要是因为我不知道自己在做什么,因为这是我第一次使用线程。以下是我的代码:

  • 进度条部分:
def loading_page(time):
    def close_loading():
        root.destroy()

    root = tk.Tk()
    root.title("Loading Page")

    root.resizable(False, False)

    loading_label = ttk.Label(root, text="Loading...", font=("Montserat", 12))
    loading_label.pack(pady=15, padx=15)

    progress_bar = ttk.Progressbar(root, length=200, mode="indeterminate")
    progress_bar.pack(padx=15, pady=15)

    progress_bar.after(time, close_loading)

    progress_bar.start(10)

    root.mainloop()
  • 线程部分:
def task(*args):
    loading_page(*args)


def thread_time_decorator(thread):
    @wraps(thread)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        thread(*args, **kwargs)
        end = time.perf_counter()
        threading.current_thread().thread_duration = end - start

    return wrapper


def worker(func, *args):
    t1 = threading.Thread(target=func, args=(*args,))
    t1.start()


def ini_task(c):
    t1 = threading.Thread(target=generate_file, args=(c,))
    wrapped_t1 = thread_time_decorator(t1)
    t1.start()
    task(wrapped_t1)
    t1.join()
  • 主窗口调用的函数部分:
def some_function(c):
    with contextlib.suppress(Exception):
        helper.close_one_drive()
    try:
        # 执行一些文件操作
    except Exception:
        if kill_prompt := messagebox.askokcancel(
            title="Excel about to close",
            message="This operation cannot be performed because the file is already opened."
            "\n If you press 'OK' to continue the process, all opened Excel files will be closed.",
        ):
            os.system("taskkill /T /IM EXCEL.exe")

            file_gen = some_function(c)
    # 这是进度条应该停止的地方,或者在整个函数完成后立即销毁
    if prompt := messagebox.askokcancel(
        title=f"Operation complete for {c}",
        message=f" Would you like to go to {c} ?",
    ):
        return c, subprocess.call(
            [file_gen],
            shell=True,
        )

请注意,我修复了代码中的HTML实体引用错误。希望这些修改有助于解决您的问题。如果您有任何其他疑问,请随时提出。

英文:

I am writing a Tkinter app and i need an indeterminate progressbar that shows over the main window, while, in the back, the function called does some operations. Because i don't know how long this operation will be, i don't want to have to time the progress bar. Also, the function called will show an info window, so, there should the progressbar stop. I've looked up multiple ways of doing it, and i think i found the right method. I want to let the function to run in a separate thread, measure the thread execution time, and then pass the time, somehow, to the progressbar to stop. I don't know if this is the right way. Also, it doesn't work, mostly because i don't know what i am doing, because is my first time working with threads. Below is my code:

  • the progress bar:
def loading_page(time):
    def close_loading():
        root.destroy()

    root = tk.Tk()
    root.title("Loading Page")

    root.resizable(False, False)

    loading_label = ttk.Label(root, text="Loading...", font=("Montserat", 12))
    loading_label.pack(pady=15, padx=15)

    progress_bar = ttk.Progressbar(root, length=200, mode="indeterminate")
    progress_bar.pack(padx=15, pady=15)

    progress_bar.after(time, close_loading())

    progress_bar.start(10)

    root.mainloop()
  • the threads:
def task(*args):
    loading_page(*args)


def thread_time_decorator(thread):
    @wraps(thread)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        thread(*args, **kwargs)
        end = time.perf_counter()
        threading.current_thread().thread_duration = end - start

    return wrapper


def worker(func, *args):
    t1 = threading.Thread(target=func, args=(*args,))
    t1.start()


def ini_task(c):
    t1 = threading.Thread(target=generate_file, args=(c,))
    wrapped_t1 = thread_time_decorator(t1)
    t1.start()
    task(wrapped_t1)
    t1.join()
  • the function that will be called from the main window:
def some_function(c):
    with contextlib.suppress(Exception):
        helper.close_one_drive()
    try:
        #some file operations
    except Exception:
        if kill_prompt := messagebox.askokcancel(
            title="Excel about to close",
            message="This operation cannot be performed because the file is already opened."
            "\n If you press 'OK' to continue the process, all opened Excel files will be closed.",
        ):
            os.system("taskkill /T /IM EXCEL.exe")

            file_gen = some_function(c)
    #this is where the progress bar shoud stop, or be destroyed immediately after the whole function finishes 
    if prompt := messagebox.askokcancel(
        title=f"Operation complete for {c}",
        message=f" Would you like to go to {c} ?",
    ):
        return c, subprocess.call(
            [file_gen],
            shell=True,
        )

答案1

得分: 0

以下是要翻译的内容:

"After some time and multiple readings of other answers on SO, i've come up with this solution, that actually solves the initial problem, using another approach than timing a thread. Below, the code:

def loading_page(thread, close_event, on_closing):
    import gc

    def close_loading():
        root.withdraw()  # 隐藏根窗口
        root.quit() 
        gc.collect()

    def check_thread():
        if thread.is_alive():
            root.after(3000, check_thread)
            root.update()  # 允许GUI更新
        else:
            close_loading()
            close_event.set()  # 设置关闭事件以指示完成

    root = tk.Tk()
    root.title("加载页面")
    root.resizable(False, False)
    root.protocol("WM_DELETE_WINDOW", on_closing)

    # 为加载消息创建标签
    loading_label = ttk.Label(root, text="加载中...", font=("Montserrat", 12))
    loading_label.pack(pady=15, padx=15)

    # 创建进度条
    progress_bar = ttk.Progressbar(root, length=200, mode="indeterminate")
    progress_bar.pack(padx=15, pady=15)

    progress_bar.start(10)
    progress_bar.after(100, progress_bar.update_idletasks)
    progress_bar.after(1000, check_thread)
    root.mainloop()


def load_proc(*args):
    t2return = ThreadWithReturnValue(target=generate_file, args=(*args,))
    t2return.start()

    close_event = Event()

    def on_closing():
        if not close_event.is_set():
            pass

    loading_thread = Thread(
        target=loading_page, args=(t2return, close_event, on_closing)
    )
    loading_thread.start()

    # 一旦主窗口关闭,设置关闭事件以停止加载窗口。
    close_event.set()

    # 等待加载线程完成。
    loading_thread.join()

    # 在加载线程完成后返回结果。
    return open_file(t2return.join())

def generate_file(*args):
    # 执行文件生成操作

ThreadwithReturnValue 实际上是从 Stack Overflow 上的另一个帖子中获取的一个类,在那个帖子中,原帖作者希望一个线程能够返回可以在函数中使用的值:

class ThreadWithReturnValue(Thread):
    def __init__(self, group=None, target=None, name=None, args=(), kwargs=None):
        Thread.__init__(self, group, target, name, args, kwargs)
        self._return = None

    def run(self):
        if self._target is not None:
            self._return = self._target(*self._args, **self._kwargs)

    def join(self, *args):
        Thread.join(self, *args)
        return self._return

因为花费了一些时间,我不能提供包含制定此解决方案所需的所有来源的列表。"

英文:

After some time and multiple readings of other answers on SO, i've come up with this solution, that actually solves the initial problem, using another approach than timing a thread. Below, the code:

def loading_page(thread, close_event, on_closing):
    import gc

    def close_loading():
        root.withdraw()  # Hide the root window
        root.quit() 
        gc.collect()

    def check_thread():
        if thread.is_alive():
            root.after(3000, check_thread)
            root.update()  # Allow the GUI to update
        else:
            close_loading()
            close_event.set()  # Set the close event to indicate completion

    root = tk.Tk()
    root.title("Loading Page")
    root.resizable(False, False)
    root.protocol("WM_DELETE_WINDOW", on_closing)

    # Create a label for the loading message
    loading_label = ttk.Label(root, text="Loading...", font=("Montserrat", 12))
    loading_label.pack(pady=15, padx=15)

    # Create a progress bar
    progress_bar = ttk.Progressbar(root, length=200, mode="indeterminate")
    progress_bar.pack(padx=15, pady=15)

    progress_bar.start(10)
    progress_bar.after(100, progress_bar.update_idletasks)
    progress_bar.after(1000, check_thread)
    root.mainloop()


def load_proc(*args):
    t2return = ThreadWithReturnValue(target=generate_file, args=(*args,))
    t2return.start()

    close_event = Event()

    def on_closing():
        if not close_event.is_set():
            pass

    loading_thread = Thread(
        target=loading_page, args=(t2return, close_event, on_closing)
    )
    loading_thread.start()

    # Once the main window is closed, set the close event to stop the loading window.
    close_event.set()

    # Wait for the loading thread to complete.
    loading_thread.join()

    # Return the result after the loading thread finishes.
    return open_file(t2return.join())

def generate_file(*args):
    #does file generation stuff

ThreadwithReturnValue is actually a class taken from another post on SO, where OP wanted a thread to return a value that can be used in a function:

class ThreadWithReturnValue(Thread):
    def __init__(self, group=None, target=None, name=None, args=(), kwargs=None):
        Thread.__init__(self, group, target, name, args, kwargs)
        self._return = None

    def run(self):
        if self._target is not None:
            self._return = self._target(*self._args, **self._kwargs)

    def join(self, *args):
        Thread.join(self, *args)
        return self._return

Because it took some time, i can't put a list with all the sources needed to come up with this solution

huangapple
  • 本文由 发表于 2023年7月6日 17:26:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/76627373.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定