Python 保持程序打开,直到按键按下-线程和子进程-不需要的按键拦截

Python 保持程序打开,直到按键按下-线程和子进程-不需要的按键拦截,python,multithreading,subprocess,Python,Multithreading,Subprocess,tl;dr:我有几个线程,其中一个线程监听input(),以保持程序在按键时运行/退出。但在程序中的某一时刻,我需要停止此侦听器,否则它将拦截子进程程序的输入 长版本: -程序应该下载一些数据,然后将其交给其他控制台程序进行处理。 -程序应该一直运行,直到下载完成或发送回车键。 -在这两种情况下,下载线程都将优雅地结束,并应完成外部处理。 -问题:input()函数仍在侦听和拦截对子进程控制台程序的第一个输入 导入操作系统 导入子流程 导入线程 导入时间 def thread_do_downlo

tl;dr:我有几个线程,其中一个线程监听input(),以保持程序在按键时运行/退出。但在程序中的某一时刻,我需要停止此侦听器,否则它将拦截子进程程序的输入

长版本:
-程序应该下载一些数据,然后将其交给其他控制台程序进行处理。
-程序应该一直运行,直到下载完成或发送回车键。
-在这两种情况下,下载线程都将优雅地结束,并应完成外部处理。
-问题:input()函数仍在侦听和拦截对子进程控制台程序的第一个输入

导入操作系统
导入子流程
导入线程
导入时间
def thread_do_downloads():
#进行一些下载,并将设置标志“flag_download_completed=True”
#最终信号下载完成
#对于本例,只需设置标志
全局标志\u下载\u完成
flag_download_completed=真
def do_stuff_带有下载的数据():
#这当然不是我所说的计划,
#但是这个例子应该说明如何截取输入
如果os.name='nt':
参数=[“set”,“/p”,“variable=按Enter”]#对于本例(Windows)调用“set”,此程序将等待用户输入
其他:
parameters=[“read”,“variable”]#希望这适用于linux。。。
p1=子流程.Popen(参数,shell=True)
p1.沟通()
def listen_查找按键()
输入()
打印(“截取按键”)
def main():
dl=threading.Thread(目标=Thread\u do\u下载)
dl.start()
kill_listener=threading.Thread(target=listen_for_keypress,daemon=True)#daemon:在主线程完成后不让它延迟
kill_listener.start()
打印(“按ENTER键停止下载”)
尽管如此:
如果没有杀死\u侦听器。是否\u活动()或标记\u下载\u已完成:
打破
时间。睡眠(1)
#这里有几行代码可以确保上面的下载线程顺利完成
用下载的数据来填充
打印(“全部完成”)
如果uuuu name uuuuuu='\uuuuuuu main\uuuuuuu':
flag_download_completed=错误
main()
将导致:
按ENTER键停止下载。

按Enter键如果可以将主线程保持在控制台顶部,则可能可以利用以下事实,即
input()
将阻止主线程,直到按下
Enter
。一旦执行继续(因为按下了Enter),请与正在运行的线程通信,告知它们必须停止使用(另一个示例)。如果您确实想监听S.O.信号,我建议您查看模块(注意,某些功能可能与O.S.有关)

编辑(根据OP的评论):

原始问题的一个复杂问题是如何将终止请求传递给子流程。因为进程不与父进程(产生它的进程)共享内存,所以实际上,这只能(或几乎只能)通过实际的SO信号来完成。由于这种内存隔离,在父进程上设置的任何标志在派生的子进程中都不会产生任何影响:进程间通信的唯一方式是通过操作系统信号,或者通过父进程和子进程“已知”并用于共享信息的文件(或类似文件的结构)。此外,在父进程中调用
input()
会将标准输入(
stdin
)绑定到该进程,这意味着默认情况下,子进程不知道在父进程中按下的键(您始终可以将子进程的
stdin
绑定到父进程的
stdin
,但这会使代码更加复杂)

幸运的是,do的实例提供了一种向子进程发送信号的好方法:子进程可以捕捉到的信号,应该解释为“嘿,你很快就会被停止,所以你要清理东西,关闭文件等等,然后退出”和没有真正告诉子进程任何信息的信号(无法捕获):它只是将其杀死(例如,在Linux中,KILL信号将从被杀死的进程中删除对内存的所有访问,因此任何使用内存的操作(例如查找下一个操作)都将导致错误。更多信息)

为了证明这一点,假设我们在主程序所在的同一目录中有一个简单的
script.py
文件,如下所示:

script.py>

一个需要随机时间处理的脚本,可能相当长(至少足够长,可以演示)

现在,我们可以在运行
script.py
文件的tread中创建一个(或多个)子进程,定期检查它们的状态(使用),如果用户请求强制输出,则发送
TERM
信号,稍后在必要时发送
KILL

import threading
import time
import subprocess


def thread_do_downloads(stop_activated):
    p = subprocess.Popen('./script.py', stdout=subprocess.PIPE)
    while p.poll() is None:
        time.sleep(0.5)
        print("Subprocess still running... Slepping a bit... ZzzzzzZZZ")
        if stop_activated.is_set():
            print("Forcing output requested!!!")
            print("Trying to terminate the process nicely, which a SIGTERM:")
            p.terminate()
            time.sleep(0.5)
            if p.poll() is None:
                print("Not being nice anymore... Die, die die!!")
                p.kill()
            print("This is what the subprocess 'said':\n%s" % p.stdout.read())
            return
    print("stopping normally")


def do_stuff_with_downloaded_data():
    print("doing stuff with downloaded data")


def listen_for_keypress(stop_activated):
    input("Press ENTER to stop downloading.")
    print("keypress intercepted")
    stop_activated.set()


def main():
    stop_activated = threading.Event()

    dl = threading.Thread(target=thread_do_downloads, args=(stop_activated,))
    dl.start()

    kill_listener = threading.Thread(target=listen_for_keypress, args=(stop_activated,), daemon=True)
    kill_listener.start()

    dl.join()
    print("Finished downloading data")

    # here are some lines to make sure the download thread above completes gracefully
    do_stuff_with_downloaded_data()
    print("All done")


if __name__ == '__main__':
    main()

谢谢,但这将迫使我按ENTER键,因为主线程(?)中有
input()
。这正是我不想要的。只有当我想在程序停止前中止程序时才需要按ENTER键(因为下载已完成)。啊,我明白了,我明白了(我想)…检查编辑?感谢您的努力和编辑,但这也不起作用。您必须在代码中保留子流程行。这些是导致问题的关键行。因为keylistener仍将运行,并且仍将拦截按键到达子流程。如果没有子流程,整个问题将无法解决一开始就不存在。thread.stop()在这里会非常有用,但不幸的是它不存在。我来自一个居民以其固执而闻名的地区:-D I e
#!/usr/bin/env python

import sys
import random
import time


def main():
    done = False
    while not done:
        time.sleep(0.5)
        print("I'm busy doing things!!")
        done = random.randint(0, 15) == 1


if __name__ == "__main__":
    main()
    sys.exit(0)  # This is pretty much unnecessary, though
import threading
import time
import subprocess


def thread_do_downloads(stop_activated):
    p = subprocess.Popen('./script.py', stdout=subprocess.PIPE)
    while p.poll() is None:
        time.sleep(0.5)
        print("Subprocess still running... Slepping a bit... ZzzzzzZZZ")
        if stop_activated.is_set():
            print("Forcing output requested!!!")
            print("Trying to terminate the process nicely, which a SIGTERM:")
            p.terminate()
            time.sleep(0.5)
            if p.poll() is None:
                print("Not being nice anymore... Die, die die!!")
                p.kill()
            print("This is what the subprocess 'said':\n%s" % p.stdout.read())
            return
    print("stopping normally")


def do_stuff_with_downloaded_data():
    print("doing stuff with downloaded data")


def listen_for_keypress(stop_activated):
    input("Press ENTER to stop downloading.")
    print("keypress intercepted")
    stop_activated.set()


def main():
    stop_activated = threading.Event()

    dl = threading.Thread(target=thread_do_downloads, args=(stop_activated,))
    dl.start()

    kill_listener = threading.Thread(target=listen_for_keypress, args=(stop_activated,), daemon=True)
    kill_listener.start()

    dl.join()
    print("Finished downloading data")

    # here are some lines to make sure the download thread above completes gracefully
    do_stuff_with_downloaded_data()
    print("All done")


if __name__ == '__main__':
    main()