Python3 诅咒线程

Python3 curses with threading

我有一个使用 curses 和线程的应用程序,它等待输入,然后将其发布到队列中。屏幕很快就会被奇怪的字符损坏。最初我尝试将 'with threading.Lock()' 添加到与 curses 相关的所有内容,以防止线程相互破坏。那没有用。

为了简单起见,我将应用程序提炼为我能想出的最短代码来显示问题。在代码中,我从线程向屏幕添加一个字符串,然后进行刷新。很快错误就会出现在整个屏幕上。这可能是由于转义序列被写入屏幕并在写入过程中被另一个线程中断。

是否有正确或更聪明的方法,或巧妙的技巧来让 curses 与其他线程很好地配合?

#!/usr/bin/python3
# date: 2020.02.29 
# platform: raspberry pi 3b+
# python version: 3.5.3
#
# intent: figure out how to get threads to pass messages to the main thread
#         without failure. failure: exiting unexpectedly, throwing exceptions, or corrupting the display.
#
# -v0.0: no thread locking; 5 threads; fails almost instantly.
# -v0.1: thread locking every call to curses methods after threads started; still fails.
# -v0.2: reduced # of threads to 1; takes longer to fail.

import sys,os,time,curses,threading

def threadfunc(ch,blocktime,stdscr):
    while True:
        threadname = 'thread {}'.format(ch)
        with threading.Lock():
            stdscr.addstr(int(curses.LINES/3)-2,int((curses.COLS - len(threadname))/2),threadname)
            stdscr.refresh()
        time.sleep(blocktime)

def main(stdscr):
    if curses.has_colors() == True:
        curses.start_color()
        curses.use_default_colors()
        curses.init_pair(1,curses.COLOR_GREEN,curses.COLOR_BLUE)
        curses.init_pair(2,curses.COLOR_WHITE,curses.COLOR_RED)
        stdscr.bkgd(' ',curses.color_pair(1))

    curses.curs_set(0)      # cursor off.
    curses.noecho()
    curses.cbreak()
    stdscr.keypad(True)     # receive special messages.

    # instantiate a small window to hold responses to keyboard messages.
    xmsg = 32
    ymsg = 1
    msgwin = curses.newwin(ymsg,xmsg,int(curses.LINES/3),int((curses.COLS - xmsg)/2))
    msgwin.bkgd(' ',curses.color_pair(2))
    stdscr.noutrefresh()
    msgwin.noutrefresh()
    curses.doupdate()

    # make threads, each with slightly different sleep time:
    threadcount = 5
    t = []
    for i in range(threadcount):
        t.append(threading.Thread(target=threadfunc,name='t{}'.format(i),args=(chr(ord('0')+i),0.2+0.02*i,stdscr),daemon=True))
        t[i].start()

    while True:
        with threading.Lock():
            key = stdscr.getch()    # wait for a character; returns an int; does not raise an exception.
        if key == 0x1b:             # escape key exits
            exitmsg = 'exiting...'
            with threading.Lock():
                msgwin.erase()
                msgwin.addstr(0,int((xmsg-len(exitmsg))/2),exitmsg)
            break
        else:
            feedback = 'received {}'.format(chr(key))
            with threading.Lock():
                msgwin.erase()
                msgwin.addstr(0,int((xmsg-len(feedback))/2),feedback)

        with threading.Lock():
            msgwin.refresh()

    del t           # is this the proper way to destroy an object?
    exitmsg = 'press any key to exit'
    stdscr.addstr(int(curses.LINES/2),int((curses.COLS-len(exitmsg))/2),exitmsg)
    stdscr.getkey()

    stdscr.keypad(False)
    curses.nocbreak()
    curses.echo()
    curses.endwin()

if __name__ == '__main__':
    # Must happen BEFORE calling the wrapper, else escape key has a 1 second delay after pressing:
    os.environ.setdefault('ESCDELAY','100') # in mS; default: 1000
    curses.wrapper(main)

诀窍是使用 noutrefresh()/doupdate() 而不是 refresh() 调用。暂存更改,然后在一个线程中处理 doupdate()

#!/usr/bin/python3
# date: 2020.02.29 
# platform: raspberry pi 3b+
# python version: 3.5.3
#
# intent: figure out how to get threads to pass messages to the main thread
#         without failure. failure: exiting unexpectedly, throwing exceptions, or corrupting the display.
#
# -v0.0: no thread locking; 5 threads; fails almost instantly.
# -v0.1: thread locking every call to curses methods after threads started; still fails.
# -v0.2: reduced # of threads to 1; takes longer to fail.
# -v0.3: no thread locking; using redrawln, redrawwin to fix corruption
# -v0.4: no redrawln; use noutrefresh/doupdate instead of refresh

import sys,os,time,curses,threading

import locale

locale.setlocale(locale.LC_ALL, '')
code = locale.getpreferredencoding()

def threadfunc(ch,blocktime,stdscr):
    while True:
        threadname = 'thread {}'.format(ch)
        stdscr.addstr(int(curses.LINES/3)-2,int((curses.COLS - len(threadname))/2),threadname)
        # stdscr.redrawln(int(curses.LINES/3)-2, 1)
        stdscr.noutrefresh()
        curses.doupdate()
        time.sleep(blocktime)

def main(stdscr):
    if curses.has_colors() == True:
        curses.start_color()
        curses.use_default_colors()
        curses.init_pair(1,curses.COLOR_GREEN,curses.COLOR_BLUE)
        curses.init_pair(2,curses.COLOR_WHITE,curses.COLOR_RED)
        stdscr.bkgd(' ',curses.color_pair(1))

    curses.curs_set(0)      # cursor off.
    curses.noecho()
    curses.cbreak()
    stdscr.keypad(True)     # receive special messages.

    # instantiate a small window to hold responses to keyboard messages.
    xmsg = 32
    ymsg = 1
    msgwin = curses.newwin(ymsg,xmsg,int(curses.LINES/3),int((curses.COLS - xmsg)/2))
    msgwin.bkgd(' ',curses.color_pair(2))

    stdscr.noutrefresh()
    msgwin.noutrefresh()
    curses.doupdate()

    # make threads, each with slightly different sleep time:
    threadcount = 5
    t = []
    for i in range(threadcount):
        t.append(threading.Thread(target=threadfunc,name='t{}'.format(i),args=(chr(ord('0')+i),0.2+0.02*i,stdscr),daemon=True))
        t[i].start()

    while True:
        key = stdscr.getch()    # wait for a character; returns an int; does not raise an exception.
        if key == 0x1b:             # escape key exits
            exitmsg = 'exiting...'
            msgwin.erase()
            msgwin.addstr(0,int((xmsg-len(exitmsg))/2),exitmsg)
            break
        else:
            feedback = 'received {}'.format(chr(key))
            msgwin.erase()
            msgwin.addstr(0,int((xmsg-len(feedback))/2),feedback)
        msgwin.noutrefresh()

    del t           # is this the proper way to destroy an object?
    exitmsg = 'press any key to exit'
    stdscr.addstr(int(curses.LINES/2),int((curses.COLS-len(exitmsg))/2),exitmsg)
    stdscr.getkey()

    stdscr.keypad(False)
    curses.nocbreak()
    curses.echo()
    curses.endwin()

if __name__ == '__main__':
    # Must happen BEFORE calling the wrapper, else escape key has a 1 second delay after pressing:
    os.environ.setdefault('ESCDELAY','100') # in mS; default: 1000
    curses.wrapper(main)