Search code examples
pythonmultithreadingeasygui

How to stop a thread that has a blocking function from easygui in python


Context

I am using the easygui library to make a simple interface for my program.

In the program, I need to start a process that takes time and the user can cancel it if he is tired of waiting.

Code

Here is a minimal example code:

import easygui as eg
import threading
import time

stop_process_flag = False

def try_connection():
    global stop_process_flag
    
    while True:
        time.sleep(1)
        
        if stop_process_flag:
            return False
        
        # "foo" will eventually connect (return a True value)
        connected = foo()
        
        if connected:
            return True
        
def end_connection():
    global stop_process_flag
    
    stop = eg.msgbox('Click ok to stop process before completion')
    # code does not reach here if the user does not click ok in the easygui box
    if stop:
        stop_process_flag = True

# create threads
process_thread = threading.Thread(target=try_connection)
monitor_thread = threading.Thread(target=end_connection)

# start threads
process_thread.start()
monitor_thread.start()

# Wait the process to finish. It can happen due to successful completion or user cancellation
process_thread.join()

# Check if the user has clicked the ok button and ended the thread
if monitor_thread.is_alive():
    # Here i need to stop the thread to close the eg.msgbox

# code continues

The goal

I want to close the msgbox if the connection is successful and the user never clicked ok to cancel the connection.

The problem

The problem is that if the user never clicks ok, the msgbox will never return and the code will never reach any line after the msgbox line.

So I can't even add a flag to the end_connection function because there will never be a chance for the function to check it.

Main question

How can I kill this monitor_thread "gracefully"?

Or, is there another way to achieve the desired result?

Outro

Let me know if you need more code or information to understand something. The actual code has a lot of unnecessary things that may draw attention from the actual problem.


Solution

  • You could use the multiprocessing module instead of threading because it allow you to terminate a process whereas thread dont do it properly.

    Here is the modified code:

    import easygui as eg
    import multiprocessing
    import time
    
    stop_process_flag = multiprocessing.Value('b', False)
    
    def try_connection(stop_process_flag):
        while True:
            time.sleep(1)
            
            if stop_process_flag.value:
                return False
            
            connected = foo()
            
            if connected:
                return True
            
    def end_connection(stop_process_flag):
        stop = eg.msgbox('Click ok to stop process before completion')
        
        if stop:
            with stop_process_flag.get_lock():
                stop_process_flag.value = True
    
    process_process = multiprocessing.Process(target=try_connection, args=(stop_process_flag,))
    monitor_process = multiprocessing.Process(target=end_connection, args=(stop_process_flag,))
    
    process_process.start()
    monitor_process.start()
    
    process_process.join()
    
    if monitor_process.is_alive():
        monitor_process.terminate()
        monitor_process.join()