简体   繁体   English

使用输出和超时启动python进程

[英]Start python Process with output and timeout

I'm trying to find the way to start a new Process and get its output if it takes less than X seconds. 我试图找到一种方法来启动一个新的流程,并在少于X秒的时间内获取其输出。 If the process takes more time I would like to ignore the Process result, kill the Process and carry on. 如果该过程花费更多时间,我想忽略该过程的结果,请终止该过程并继续。

I need to basically add the timer to the code below. 我基本上需要将计时器添加到下面的代码中。 Now sure if there's a better way to do it, I'm open to a different and better solution. 现在,确定是否有更好的方法可以使用,我愿意采用其他更好的解决方案。

from multiprocessing import Process, Queue

def f(q):
    # Ugly work
    q.put(['hello', 'world'])

if __name__ == '__main__':
    q = Queue()
    p = Process(target=f, args=(q,))
    p.start()
    print q.get()
    p.join()

Thanks! 谢谢!

You may find the following module useful in your case: 您可能会发现以下模块对您有用:

Module

#! /usr/bin/env python3
"""Allow functions to be wrapped in a timeout API.

Since code can take a long time to run and may need to terminate before
finishing, this module provides a set_timeout decorator to wrap functions."""

__author__ = 'Stephen "Zero" Chappell ' \
             '<stephen.paul.chappell@atlantis-zero.net>'
__date__ = '18 December 2017'
__version__ = 1, 0, 1
__all__ = [
    'set_timeout',
    'run_with_timeout'
]

import multiprocessing
import sys
import time

DEFAULT_TIMEOUT = 60


def set_timeout(limit=None):
    """Return a wrapper that provides a timeout API for callers."""
    if limit is None:
        limit = DEFAULT_TIMEOUT
    _Timeout.validate_limit(limit)

    def wrapper(entry_point):
        return _Timeout(entry_point, limit)

    return wrapper


def run_with_timeout(limit, polling_interval, entry_point, *args, **kwargs):
    """Execute a callable object and automatically poll for results."""
    engine = set_timeout(limit)(entry_point)
    engine(*args, **kwargs)
    while engine.ready is False:
        time.sleep(polling_interval)
    return engine.value


def _target(queue, entry_point, *args, **kwargs):
    """Help with multiprocessing calls by being a top-level module function."""
    # noinspection PyPep8,PyBroadException
    try:
        queue.put((True, entry_point(*args, **kwargs)))
    except:
        queue.put((False, sys.exc_info()[1]))


class _Timeout:
    """_Timeout(entry_point, limit) -> _Timeout instance"""

    def __init__(self, entry_point, limit):
        """Initialize the _Timeout instance will all needed attributes."""
        self.__entry_point = entry_point
        self.__limit = limit
        self.__queue = multiprocessing.Queue()
        self.__process = multiprocessing.Process()
        self.__timeout = time.monotonic()

    def __call__(self, *args, **kwargs):
        """Begin execution of the entry point in a separate process."""
        self.cancel()
        self.__queue = multiprocessing.Queue(1)
        self.__process = multiprocessing.Process(
            target=_target,
            args=(self.__queue, self.__entry_point) + args,
            kwargs=kwargs
        )
        self.__process.daemon = True
        self.__process.start()
        self.__timeout = time.monotonic() + self.__limit

    def cancel(self):
        """Terminate execution if possible."""
        if self.__process.is_alive():
            self.__process.terminate()

    @property
    def ready(self):
        """Property letting callers know if a returned value is available."""
        if self.__queue.full():
            return True
        elif not self.__queue.empty():
            return True
        elif self.__timeout < time.monotonic():
            self.cancel()
        else:
            return False

    @property
    def value(self):
        """Property that retrieves a returned value if available."""
        if self.ready is True:
            valid, value = self.__queue.get()
            if valid:
                return value
            raise value
        raise TimeoutError('execution timed out before terminating')

    @property
    def limit(self):
        """Property controlling what the timeout period is in seconds."""
        return self.__limit

    @limit.setter
    def limit(self, value):
        self.validate_limit(value)
        self.__limit = value

    @staticmethod
    def validate_limit(value):
        """Verify that the limit's value is not too low."""
        if value <= 0:
            raise ValueError('limit must be greater than zero')

To use, see the following example that demonstrates its usage: 要使用,请参见以下示例,演示其用法:

Example

from time import sleep


def main():
    timeout_after_four_seconds = timeout(4)
    # create copies of a function that have a timeout
    a = timeout_after_four_seconds(do_something)
    b = timeout_after_four_seconds(do_something)
    c = timeout_after_four_seconds(do_something)
    # execute the functions in separate processes
    a('Hello', 1)
    b('World', 5)
    c('Jacob', 3)
    # poll the functions to find out what they returned
    results = [a, b, c]
    polling = set(results)
    while polling:
        for process, name in zip(results, 'abc'):
            if process in polling:
                ready = process.ready
                if ready is True:       # if the function returned
                    print(name, 'returned', process.value)
                    polling.remove(process)
                elif ready is None:     # if the function took too long
                    print(name, 'reached timeout')
                    polling.remove(process)
                else:                   # if the function is running
                    assert ready is False, 'ready must be True, False, or None'
        sleep(0.1)
    print('Done.')


def do_something(data, work):
    sleep(work)
    print(data)
    return work


if __name__ == '__main__':
    main()

Does the process you are running involve a loop? 您正在运行的进程是否涉及循环? If so you can get the timestamp prior to starting the loop and include an if statement within the loop with an sys.exit(); 如果是这样,则可以在开始循环之前获取时间戳,并在循环内使用sys.exit()包含if语句; command terminating the script if the current timestamp differs from the recorded start time stamp by more than x seconds. 如果当前时间戳与记录的开始时间戳相差超过x秒,则终止脚本的命令。

All you need to adapt the queue example from the docs to your case is to pass the timeout to the q.get() call and terminate the process on timeout: 您需要根据文档调整队列示例以适应您的情况,是将超时传递给q.get()调用,并在超时时终止进程:

from Queue import Empty
...

try:
    print q.get(timeout=timeout)
except Empty: # no value, timeout occured
    p.terminate()
    q = None # the queue might be corrupted after the `terminate()` call
p.join()

Using a Pipe might be more lightweight otherwise the code is the same (you could use .poll(timeout) , to find out whether there is a data to receive). 使用Pipe可能更轻巧,否则代码是相同的(您可以使用.poll(timeout)来确定是否有要接收的数据)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM