【问题标题】:How can I end an infinite loop with socket operations inside after finishing current iteration?完成当前迭代后,如何在内部使用套接字操作结束无限循环?
【发布时间】:2016-12-07 10:43:11
【问题描述】:

我有一个无限循环,其中有一些操作必须在退出循环之前完全执行。也就是说,我正在使用套接字库连接到外部设备,我需要等待读取指令完成才能中断循环。

我尝试使用信号处理程序(如this question)在检测到键盘中断时引发标志。

当前代码:

import videosensor
import signal

def signal_handler(signal, frame):
    """Raises a flag when a keyboard interrupt is raised."""
    global interrupted
    interrupted = True

if __name__ == '__main__':
    camera = videosensor.VideoSensor(filename)
    interrupted = False
    signal.signal(signal.SIGINT, signal_handler)

    while not interrupted:
        location = camera.get_register()
        #...
        #More irrelevant stuff is executed.
        #...
        time.sleep(0.01)

    #This code has to be executed after exiting while loop
    camera_shutdown(camera)

在前面的代码中,videosensor.VideoSensor 是一个包含用于从外部设备获取数据的套接字操作的类。主程序中使用的get_register()方法如下:

def get_register(self):
    """Read the content of the specified register.
    """
    #Do some stuff
    value = socket.recv(2048)
    return value

问题:

我希望 while 循环持续执行,直到用户按下一个键或使用键盘中断,但在当前迭代完成之后。相反,使用之前的解决方案不能正常工作,因为它会中断正在进行的指令,并且如果它正在读取套接字,则会引发错误:

/home/.../client.pyc 在 read_register(self, regkey) 中

    164         reg = self._REGISTERS[regkey]
    165         self.send('r,{}\n'.format(reg))
--> 166         value = socket.recv(2048)
    167         #Convert the string input into a valid value e.g. list or int
    168         formatted_result = ast.literal_eval(value)

错误:[Errno 4] 系统中断


编辑:从下面的答案看来,没有办法使用 键盘中断 并避免套接字读取功能被中止。尽管有捕捉错误的解决方案,但它们并不能避免读取取消。

不过,我有兴趣找到一种获取用户输入的方法,例如特定的按键,会引发标志,将在循环结束时进行检查,在此检查之前不会中断主程序的执行。

EDIT2:使用的操作系统是Linux发行版Ubuntu 14.04

【问题讨论】:

  • @zmo 我编辑了内容,说明了为什么该解决方案不适合。无论如何,谢谢你的提议。
  • 您在什么平台上运行您的代码 - Windows 或 Unix?
  • @mhawke 我使用的是 Unix,即 Ubuntu 14.04
  • @Jalo:好的,看看我的回答,看看它是否适合你。

标签: python sockets interrupt


【解决方案1】:

经过快速搜索,我找到了this solution for your issue

基本上,您无能为力:当您向进程发送 SIGINT 时,套接字也会返回 SIGINT。那么,你能做的最好的事情就是主动忽略这个问题,通过捕获套接字EINTR 错误并继续你的循环:

import errno

try:
    # do something
    value = conn.recv(2048)
except socket.error as (code, msg):
    if code != errno.EINTR:
        raise

避免C-c 中断读取问题的替代解决方案是使用并行执行,在例程中读取您的套接字,并在另一方面处理用户输入:

import asyncio

async def camera_task(has_ended, filename):
    camera = videosensor.VideoSensor(filename)

    try:
        while not has_ended.is_set():
            location = camera.get_register()
            #...
            #More irrelevant stuff is executed.
            #...
            await asyncio.sleep(0.01)
    finally:
        #This code has to be executed after exiting while loop
        camera_shutdown(camera)

async def input_task(shall_end):
    while True:
        i = input("Press 'q' to stop the script…")
        if i == 'q':
            shall_end.set()

def main():
    filename = …
    #
    end_event = asyncio.Event()
    asyncio.Task(camera_task(end_event, filename))
    asyncio.Task(input_task(end_event))
    asyncio.get_event_loop().run_forever()

threading

import threading, time

def camera_task(has_ended, filename):
    camera = videosensor.VideoSensor(filename)

    try:
        while not has_ended.is_set():
            location = camera.get_register()
            #...
            #More irrelevant stuff is executed.
            #...
            time.sleep(0.01)
    finally:
        #This code has to be executed after exiting while loop
        camera_shutdown(camera)

def input_task(shall_end):
    while True:
        i = input("Press 'q' to stop the script…")
        if i == 'q':
            shall_end.set()

def main():
    filename = …
    #
    end_event = threading.Event()
    threads = [
        threading.Thread(target=camera_task, args=(end_event, filename)),
        threading.Thread(target=input_task, args=(end_event,))
    ]
    # start threads
    for thread in threads:
        thread.start()
    # wait for them to end
    for thread in threads:
        thread.join()

multiprocessing:

import multiprocessing, time

def camera_task(has_ended, filename):
    camera = videosensor.VideoSensor(filename)

    try:
        while not has_ended.is_set():
            location = camera.get_register()
            #...
            #More irrelevant stuff is executed.
            #...
            time.sleep(0.01)
    finally:
        #This code has to be executed after exiting while loop
        camera_shutdown(camera)

def input_task(shall_end):
    while True:
        i = input("Press 'q' to stop the script…")
        if i == 'q':
            shall_end.set()

def main():
    filename = …
    #
    end_event = multiprocessing.Event()
    processes = [
        multiprocessing.Process(target=camera_task, args=(end_event, filename)),
        multiprocessing.Process(target=input_task, args=(end_event,))
    ]
    # start processes
    for process in processes:
        process.start()
    # wait for them to end
    for process in processes:
        process.join()

免责声明:这些代码未经测试,可能存在一些拼写错误或小错误,但我认为整体逻辑应该是?

【讨论】:

  • 感谢您的建议。但是,我的主要问题是避免 socket.recv() 在用户请求中断时停止接收数据。使用您提出的解决方案,错误将被忽略,但 recv() 调用也将被中止......我的问题更倾向于寻找一种获取用户输入的替代方法,不一定是键盘中断,这会引发将在循环结束时检查的标志。
  • 好吧,我可能是错的,所以请用真实的代码检查,但我希望你不会在EINTR上丢失数据,缓冲的数据将通过以下调用返回,这就是为什么在点击C-c 时忽略EINTR 异常应该对您有用。否则,您的解决方案是将您的套接字内容放在线程、分叉进程或异步协程中,并让另一个处理 CLI 用户输入(带有一点提示:press 'q' to stop…
  • 我使用asyncio 更新了一个解决方案。它不涉及线程的复杂性和风险,也没有进程的成本。它只是将从最近的 py3 开始运行的代码。
  • 很抱歉,我的代码适用于 Python 2.7,而且似乎 asyncio 库仅在 Python3 中可用。 Python2 有类似的解决方案吗?
  • 第一个选项对我仍然无效。似乎当中断时,连接会中断并且任何数据都会被存储。我在使用“值”的下一行代码中出现错误,说 UnboundLocalError: local variable 'result' referenced before assignment
【解决方案2】:

您创建了自定义信号处理程序,但没有覆盖默认的键盘中断行为。将signal.signal(signal.SIGINT, signal_handler) 添加到您的代码中以完成此操作:

import videosensor
import signal

# Custom signal handler
def signal_handler(signal, frame):
    """Raises a flag when a keyboard interrupt is raised."""
    global interrupted
    interrupted = True

# Necessary to override default keyboard interrupt
signal.signal(signal.SIGINT, signal_handler)  

if __name__ == '__main__':
    # Main programme

【讨论】:

  • 对不起,我实际上在我的代码中编写了该指令,但我忘记将它放在 OP 中。我已经编辑过了。那不是问题,那么……还是谢谢你的回答。
【解决方案3】:

如果我理解正确,您不希望 socket.recv() 被中断,但您确实希望使用信号让用户指示 I/O 循环应在当前 I/O 操作完成后终止。

假设您在 Unix 系统上使用 Python 2,您可以通过在进入循环之前调用 signal.siginterrupt(signal.SIGINT, False) 来解决您的问题。这将导致系统调用在信号发生时重新启动,而不是中断它并引发异常。

在您的情况下,这意味着 socket.recv() 操作将在您的信号处理程序被调用后重新启动,因此 get_register() 在套接字上收到消息之前不会返回。如果这是您想要的,您的代码将是:

    interrupted = False
    old_handler = signal.signal(signal.SIGINT, signal_handler)    # install signal handler
    signal.siginterrupt(signal.SIGINT, False)                     # do not interrupt system calls

    while not interrupted:
        location = camera.get_register()
        if location == '':
            # remote connection closed
            break
        #...
        #More irrelevant stuff is executed.
        #...
        time.sleep(0.01)

这是一种方法,但它确实要求您的代码在 Unix 平台上运行。

另一种可能适用于其他平台的方法是处理异常,忽略进一步的SIGINT 信号(以防用户再次点击中断),然后在从get_register() 返回之前执行最终的socket.recv()功能:

import errno

def get_register(s):
    """Read the content of the specified register.
    """
    #Do some stuff
    try:
        old_handler = None
        return s.recv(2048)
    except socket.error as exc:
        if exc.errno == errno.EINTR:
            old_handler = signal.signal(signal.SIGINT, signal.SIG_IGN)    # ignore this signal
            return s.recv(2048)    # system call was interrupted, restart it
        else:
            raise
    finally:
        if old_handler is not None:
            signal.signal(signal.SIGINT, old_handler)    # restore handler

信号处理可能会变得棘手,并且上面可能存在我不知道的竞争条件。如果可能,请尝试使用siginterrupt()

【讨论】:

  • 我试过了,它失败了大约 20% 的时间。就像在下面提出的解决方案中一样,它在使用“值”的下一行代码中引发错误,说 UnboundLocalError: local variable 'result' referenced before assignment。我猜当中断与recv指令不一致的时候就是代码工作的时候。
  • 不管怎样,你写if声明有什么特殊原因吗?
  • @Jalo:你是指哪个if
  • @Jalo:关于您看到的错误,您使用的是我展示的两种解决方案中的哪一种? siginterrupt() 应该可以正常工作。我还刚刚注意到,您还在 recv() 之前调用了 send() 函数 - 如果使用第二个解决方案,您需要确保它也不受信号影响。最后,访问未绑定的变量更可能是代码中的错误,其中您假设变量绑定到某个值,但异常阻止了这种情况的发生。在我展示的代码中,直接返回值而不使用任何变量。
  • 我尝试了第一种方案,参考if location='':声明。 get_register 方法首先发送一个值指示它要读取的寄存器,然后读取客户端应该发回的内容。关于未绑定的变量,resultvalue是同一个变量,但是我这里写消息的时候搞错了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-24
  • 1970-01-01
  • 1970-01-01
  • 2017-01-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多