Search code examples
pythonpython-3.xtkinterapscheduler

apscheduler does not remove job


I'm a beginner in python and in my tkinter, I have an option menu with a total of 4 options.

Option Menu

# create tk variable
self.timervar = tk.StringVar(root)

# dropdown dictionary
self.timerDict = {"-", "5 seconds", "10 seconds", "15 seconds"}
self.timervar.set("-")  # <-- set the default value

# timer dropdown menu
self.timer_option = tk.OptionMenu(root, self.timervar, *self.timerDict, command=self.req_timer)
self.timer_option.grid(row=1, column=3, columnspan=2, padx=3, pady=3)

Based on the option, a function will run at every x minutes. When I switch to another option, the apscheduler job for the function is removed and a new job will start.

apscheduler

def req_timer(self, option):

    scheduler = apscheduler.schedulers.background.BackgroundScheduler()

    if option == "15 minutes":
        if 'job' in locals():
            job.remove_job('option_timer')
            job = scheduler.add_job(self.req_client, 'cron', second='*/15', id='option_timer')
            scheduler.start()
        else:
            job = scheduler.add_job(self.req_client, 'cron', second='*/15', id='option_timer')
            scheduler.start()

    elif option == "10 minutes":
        if 'job' in locals():
            job.remove_job('option_timer')
            job = scheduler.add_job(self.req_client, 'cron', second='*/10', id='option_timer')
            scheduler.start()
        else:
            job = scheduler.add_job(self.req_client, 'cron', second='*/10', id='option_timer')
            scheduler.start()

    elif option == "5 minutes":
        if 'job' in locals():
            job.remove_job('option_timer')
            job = scheduler.add_job(self.req_client, 'cron', second='*/5', id='option_timer')
            scheduler.start()
        else:
            job = scheduler.add_job(self.req_client, 'cron', second='*/5', id='option_timer')
            scheduler.start()

    elif option == "-":
        if 'job' in locals():
            job.remove_job('option_timer')
        else:
            pass

But in my case once the apscheduler job starts running, it doesn't stop even when I switch to a different option which should have removed the job. It stacks the job. So if I select the 5 seconds option, it would run normally. But if I switch to the 10 seconds option, it will run the 10 seconds option on top of the 5 seconds option. Since both options uses the same function, it will give me the same results twice when the 5 and 10 seconds coincides with each other.

I've tried this with and without the job id with the same results.

These are the results

Connecting to port...  # <-- 5 seconds option
Successfully connected to port 9998
Successfully connected to port 9999
Sending request  1 ...
Received reply  1 [ Sensor: 6 :: Data: 123456789 :: Client: 9100 ]
Sending request  2 ...
Received reply  2 [ Sensor: 7 :: Data: 987654321 :: Client: 9101 ]
Connecting to port...  # <-- at this point, the 10 seconds option was selected
Successfully connected to port 9998
Successfully connected to port 9999
Sending request  1 ...
Connecting to port...
Successfully connected to port 9998
Successfully connected to port 9999
Sending request  1 ...
Received reply  1 [ Sensor: 6 :: Data: 123456789 :: Client: 9100 ]
Sending request  2 ...
Received reply  1 [ Sensor: 6 :: Data: 123456789 :: Client: 9100 ]
Sending request  2 ...
Received reply  2 [ Sensor: 7 :: Data: 987654321 :: Client: 9101 ]
Received reply  2 [ Sensor: 7 :: Data: 987654321 :: Client: 9101 ]
Connecting to port...
Successfully connected to port 9998
Successfully connected to port 9999
Sending request  1 ...
Received reply  1 [ Sensor: 6 :: Data: 123456789 :: Client: 9100 ]  # <-- closed tkinter window
Execution of job "MainApplication.req_client (trigger: cron[second='*/5'], next run at: 2017-12-29 16:34:30 +08)" skipped: maximum number of running instances reached (1)
Execution of job "MainApplication.req_client (trigger: cron[second='*/5'], next run at: 2017-12-29 16:34:35 +08)" skipped: maximum number of running instances reached (1)
[Cancelled]

What's wrong with my code (probably everything) and how do I get it to run as intended?


Solution

  • job will never be in locals() because it is a local variable, and you are checking it before it is set. When the function returns, all local variables are unset, so the next time you call the function it will again be unset.

    Since you appear to be using classes, you should use a class variable, and initialize it to None

    class Whatever():
        def __init__(self, ...):
            self.job = None
            ...
        def req_timer(self, option=None):
            if self.job is not None:
                ...
    

    Also, you have a lot of duplicated code that can be removed. For example, you might want to consider rewriting the function like this to reduce the replication:

    def req_timer(self, option):
    
        seconds_map = {
            "15 minutes": "*/15",
            "10 minutes": "*/10",
            "5 minutes": "*/5",
            "-": None,
        }
    
        scheduler = apscheduler.schedulers.background.BackgroundScheduler()
    
        if self.job is not None:
            self.job.remove_job('option_timer')
            self.job = None
    
        seconds = seconds_map.get(option, None)
        if seconds is not None:
            self.job = scheduler.add_job(self.req_client, 'cron',
                                         second=seconds,
                                         id='option_timer')
            scheduler.start()