简体   繁体   English

Python3 用线程诅咒

[英]Python3 curses with threading

I have an application using curses and threading, that waits for input, then posts it to a queue.我有一个使用curses 和线程的应用程序,它等待输入,然后将其发布到队列中。 The screen very quickly becomes corrupted with odd characters.屏幕很快就会被奇怪的字符损坏。 Initially I tried adding 'with threading.Lock()' to everything that relates to curses to keep the threads from corrupting each other.最初,我尝试将“with threading.Lock()”添加到与curses 相关的所有内容中,以防止线程相互破坏。 That didn't work.那没有用。

In the interest of simplicity, I've distilled the app down to the shortest amount of code I can come up with to show the problem.为了简单起见,我将应用程序精简到我能想出的最短代码量来显示问题。 In the code, I'm adding a string to the screen from the thread, and refreshing.在代码中,我从线程向屏幕添加一个字符串,并刷新。 It doesn't take long before errors show up all over the screen.很快,错误就会出现在整个屏幕上。 It's probably caused by an escape sequence being written to the screen and being interrupted by another thread mid-write.这可能是由于转义序列被写入屏幕并在写入过程中被另一个线程中断造成的。

Is there a right or smarter way, or a clever trick to getting curses to play nicely with other threads?有没有正确或更聪明的方法,或者一个聪明的技巧来让诅咒与其他线程很好地发挥作用?

#!/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)

The trick is to use noutrefresh() / doupdate() instead of refresh() calls.诀窍是使用noutrefresh() / doupdate()而不是refresh()调用。 Stage the changes and then handle doupdate() 's in one thread. doupdate()更改,然后在一个线程中处理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)

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

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