如果后台子进程失败,如何引发异常?

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

How to raise exception if a background subprocess fails?

问题

In my python script I want to run some background subprocesses. I want python not to wait for the subprocess to finish, BUT raise an exception if the subprocess fails.

我想在我的Python脚本中运行一些后台子进程。我希望Python不等待子进程完成,但如果子进程失败,则引发异常。

I believe the typical way to run background processes is this:

我相信运行后台进程的典型方式是这样的:

subprocess.Popen("script.sh")
# Move on doing something else

但这会忽略script.sh是否以失败退出。

But this ignores if script.sh exits unsuccessfully.

但这会忽略script.sh是否以失败退出。

I know I can also do this:

我知道我也可以这样做:

process = subprocess.Popen("script.sh")
process.communicate()
if process.returncode:
    raise Exception
# Move on doing something else

但这会等待进程完成,这不是我想要的。

But this waits for the process to finish, which I don't want.

但这会等待进程完成,这不是我想要的。

For context, I expect script.sh to take ~3 seconds to run and to only fail in the worst case scenario (i.e. the failure of script.sh is not part of my program's logic, but indicates some actual, unrecoverable error). 99% of the time the user shouldn't even know that script.sh is running; but in the 1% case, it's better if python simply crashes than continue running and ignore the error.

为了上下文,我预计script.sh运行需要大约3秒,并且只有在最坏的情况下才会失败(即script.sh的失败不是我的程序逻辑的一部分,而是表示某种实际不可恢复的错误)。99%的情况下,用户甚至不应该知道script.sh正在运行;但在1%的情况下,如果Python崩溃而不是继续运行并忽略错误,那将更好。

How can I make this happen?

我该如何实现这一点?

英文:

In my python script I want to run some background subprocesses. I want python not to wait for the subprocess to finish, BUT raise an exception if the subprocess fails.

I believe the typical way to run background processes is this:

subprocess.Popen("script.sh")
# Move on doing something else

But this ignores if script.sh exits unsuccessfullly.

I know I can also do this:

process = subprocess.Popen("script.sh")
process.communicate()
if process.returncode:
    raise Exception
# Move on doing something else

But this waits for the process to finish, which I don't want.

For context, I expect script.sh to take ~3 seconds to run and to only fail in the worst case scenario (i.e. the failure of script.sh is not part of my program's logic, but indicates some actual, unrecoverable error). 99% of the time the user shouldn't even know that script.sh is running; but in the 1% case, it's better if python simply crashes than continue running and ignore the error.

How can I make this happen?

答案1

得分: 1

我想出了两个想法。你喜欢哪个取决于你的主线程实现。

第一种方法是使用poll。这更适合在等待子进程完成时执行其他操作的实现。这种方法简单,但你需要定期检查主线程中子进程的状态。

import subprocess
import time


class PollingWorker:
    def __init__(self, args):
        self.args = args
        self.process = subprocess.Popen(args)

    @property
    def return_code(self):
        return self.process.poll()


def run_polling():
    worker = PollingWorker("script.sh")

    # 继续做其他事情
    for i in range(5):
        print(i)
        time.sleep(1)

        # 但你需要定期检查return_code。
        if worker.return_code:
            print("ERROR:", worker.args)
            return

第二种方法是使用线程。这种方法允许你等待子进程完成而不阻塞主线程,通过在子线程中执行子进程。请注意,这种方法消除了定期检查,但使错误处理稍微复杂一些。

import _thread
import subprocess
import threading
import time


class ThreadWorker:
    def __init__(self, args):
        self.args = args
        self.thread = threading.Thread(target=self._run_in_thread, args=(args,))
        self.thread.start()

    def _run_in_thread(self, args):
        try:
            subprocess.run(args, check=True)
        except subprocess.CalledProcessError as e:
            print("ERROR:", args, e)
            # 请注意,此方法在子线程中执行。
            # 即在这里引发异常不会终止主线程。
            # 反而会在主线程中引发KeyboardInterrupt。
            _thread.interrupt_main()


def run_thread():
    # 你需要保持worker实例以避免垃圾回收。
    worker = ThreadWorker("script.sh")

    # 继续做其他事情
    for i in range(5):
        print(i)
        time.sleep(1)

        # 在这种情况下,你不需要关心任何事情。
英文:

I came up with two ideas. Which you prefer depends on your main thread implementation.

The first approach is to use poll. This is better suited for implementations that do something else while waiting for the sub-process to finish. This approach is simple, but you will need to periodically check the status of the sub-process in the main thread.

import subprocess
import time


class PollingWorker:
    def __init__(self, args):
        self.args = args
        self.process = subprocess.Popen(args)

    @property
    def return_code(self):
        return self.process.poll()


def run_polling():
    worker = PollingWorker("script.sh")

    # Move on doing something else
    for i in range(5):
        print(i)
        time.sleep(1)

        # But you need to check return_code periodically.
        if worker.return_code:
            print("ERROR:", worker.args)
            return

The second approach is to use threads. This approach allows you to wait for the sub-process to finish without blocking the main thread by executing the sub-process in a sub-thread. Note that this approach eliminates periodic checks, but makes error handling a bit more complicated.

import _thread
import subprocess
import threading
import time


class ThreadWorker:
    def __init__(self, args):
        self.args = args
        self.thread = threading.Thread(target=self._run_in_thread, args=(args,))
        self.thread.start()

    def _run_in_thread(self, args):
        try:
            subprocess.run(args, check=True)
        except subprocess.CalledProcessError as e:
            print("ERROR:", args, e)
            # Note that this method is executed in sub thread.
            # That is, raising an exception here will not terminate main thread.
            # Instead, following raises KeyboardInterrupt in main thread.
            _thread.interrupt_main()


def run_thread():
    # You need to keep worker instances to avoid garbage collection.
    worker = ThreadWorker("script.sh")

    # Move on doing something else
    for i in range(5):
        print(i)
        time.sleep(1)

        # This case, you have nothing to take care of.

huangapple
  • 本文由 发表于 2023年5月13日 13:01:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/76241145.html
匿名

发表评论

匿名网友

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

确定