计时器阻止程序退出

Timer preventing program from exiting

我正在编写一个程序,允许我使用 python 控制原版 Minecraft 服务器。我想做的第一件事是自动重启功能。一切正常,除了我不能做 sys.exit() 或类似的事情,我不确定,但我认为这是因为 Timer.

我尝试了 t.cancel(),但是 t 是一个局部变量,所以使用起来很复杂。

这是我的代码:

#! /usr/bin/env python3
import os, sys, time
import subprocess
from datetime import datetime, timedelta, date
from threading import Timer

server = subprocess.Popen('./start.sh', stdin=subprocess.PIPE,shell=True)
#junk variables
content = ''
previousContent = ''

def restart():
    if datetime.today().weekday() is 3:
        server.stdin.write(bytes('stop\r\n', 'ascii'))
        server.stdin.flush()
        time.sleep(90)
        print('Restarting...')
        os.system('python3 start.py')
        sys.exit()
    else:
        timerStart()

def timerStart():
    today = datetime.today()
    restartDate = today.replace(day=today.day,hour=1,minute=0,second=0,microsecond=0) + timedelta(days=1)
    delta_t = restartDate-today
    secs= delta_t.total_seconds()
    t=Timer(secs, restart)
    t.start()

timerStart()

while True:
    time.sleep(0.1)
    
    #stdout
    f = open('logs/latest.log')
    content = f.read()
    if previousContent != '':
        if previousContent in content:
            content.replace(previousContent,'')
            if content != '':
                print(content)
    previousContent = f.read()
    f.close()

    #stdin
    command = input('')
    if command:
        if command == 'stop':
            server.stdin.write(bytes('stop\r\n', 'ascii'))
            server.stdin.flush()
            time.sleep(20)
            sys.exit()
        else:
            server.stdin.write(bytes(command + '\r\n', 'ascii'))
            server.stdin.flush()

如果有人至少能让我走上正轨,那对我真的很有帮助

当实例化threading.Timer class,并在相应的对象中调用它的start()方法时,一个新的非后台执行线程被创建并在后台启动以执行该方法"重新开始”。创建的执行线程,等待实例化threading.Timerclass时“secs”变量指定的时间间隔过去,然后执行指定的“restart”方法。调用 sys.exit() 时脚本的执行没有结束的原因是因为应用程序的主执行线程为了完成其执行(以及脚本的执行)必须等待创建的所有其他非守护线程首先完成。也就是说,当您调用 sys.exit() 时,进程的主执行线程将不得不等待由 threading.Timer class 的实例化创建的线程完成其执行。要解决这个问题,正如您提到的,您可以做的是调用 t.cancel()。现在为此,您必须首先将变量“t”的声明移动到全局范围,以便能够在 'timerStart' 函数中使用它,并在执行的代码块中处理“停止”命令.例如,在声明和初始化变量“previousContent”之后,您可以在那里声明变量t并将其初始化为None。然后,在给它分配 threading.Timer 的实例之前,你必须使用允许修改当前范围之外的变量的“全局”关键字(在 'timerStart' 函数的范围之外),最后,在处理“停止”命令时,调用t.cancel()。简而言之,最终代码如下所示:

# ! /usr/bin/env python3
import os, sys, time
import subprocess
from datetime import datetime, timedelta, date
from threading import Timer

server = subprocess.Popen('./start.sh', stdin=subprocess.PIPE, shell=True)
# junk variables
content = ''
previousContent = ''
t = None


def restart():
    if datetime.today().weekday() is 3:
        server.stdin.write(bytes('stop\r\n', 'ascii'))
        server.stdin.flush()
        time.sleep(90)
        print('Restarting...')
        os.system('python3 start.py')
        sys.exit()
    else:
        timerStart()


def timerStart():
    today = datetime.today()
    restartDate = today.replace(day=today.day, hour=1, minute=0, second=0, microsecond=0) + timedelta(days=1)
    delta_t = restartDate - today
    secs = delta_t.total_seconds()
    global t
    t = Timer(secs, restart)
    t.start()


timerStart()

while True:
    time.sleep(0.1)

    # stdout
    with open('logs/latest.log') as f:
        # Is better to open the file using a "with", such that the file is guaranteed to be closed,
        # even in the case of an exception
        content = f.read()
        if previousContent != '':
            if previousContent in content:
                content.replace(previousContent, '')
                if content != '':
                    print(content)
        previousContent = f.read()

    # stdin
    command = input('')
    if command:
        if command == 'stop':
            # Abort the threading.Timer's non-daemon execution thread:
            t.cancel()
            # Blocks the calling thread until the thread whose join() method is called is terminated:
            t.join()
            server.stdin.write(bytes('stop\r\n', 'ascii'))
            server.stdin.flush()
            time.sleep(20)
            sys.exit()
        else:
            server.stdin.write(bytes(command + '\r\n', 'ascii'))
            server.stdin.flush()

此外,很高兴知道,通过实例化 threading.Timer class 创建的线程可以取消,只有在它开始执行“重启”方法之前。 class threading.Timer 继承自 threading.Thread,这是它在调用 start() 时创建并触发新的非守护进程执行线程的方式。

参考文献:

Timer Objects Thread Objects

如果您对线程所做的唯一事情就是重新启动脚本,您可以尝试更简单的方法。如果您为定时器中断设置一个信号处理程序,然后启动一个定时器,该定时器将在您希望代码重新启动时触发,您可以跳过所有线程处理。

#!/usr/bin/env python3

import signal
...other imports...

def do_restart(signal_num, frame):
    server.stdin.write(bytes('stop\r\n', 'ascii'))
    server.stdin.flush()
    time.sleep(90)
    print('Restarting...')
    os.system('python3 start.py')
        sys.exit()

...calculate how long before the restart...

signal.signal(signal.SIGALRM, do_restart)
signal.alarm(seconds_until_restart)

while True:
    time.sleep(0.1)
    
    #stdout
    f = open('logs/latest.log')
    content = f.read()
    if previousContent != '':
        if previousContent in content:
            content.replace(previousContent,'')
            if content != '':
                print(content)
    previousContent = f.read()
    f.close()

    #stdin
    command = input('')
    if command:
        if command == 'stop':
            server.stdin.write(bytes('stop\r\n', 'ascii'))
            server.stdin.flush()
            time.sleep(20)
            sys.exit()
        else:
            server.stdin.write(bytes(command + '\r\n', 'ascii'))
            server.stdin.flush()