View previous topic :: View next topic |
Author |
Message |
musv Advocate
Joined: 01 Dec 2002 Posts: 3337 Location: de
|
Posted: Wed Jul 19, 2017 3:08 pm Post subject: Python: curses and refresh on resize inside a thread |
|
|
Hi again,
due to the competent help, I'm back again with a Python problem.
I'm playing around with pycurses to develop a graphical application, which runs in a terminal. The application is multithreaded. And so I put the ncurses routine also in a separate thread. But on resizing the terminal I get a lot of garbage instead a clean refresh.
Code: | #!/usr/bin/env python3
# -*- coding: utf-8 -*-
import curses
import time
import threading
def update_time(screen,te):
while 1:
maxy,maxx = screen.getmaxyx()
foo = "x: {} - y: {}".format(maxx,maxy)
if te.is_set():
curses.resizeterm(maxy,maxx)
screen.clear()
screen.hline(3,0,curses.ACS_HLINE,maxx)
screen.move(4,0)
screen.clrtoeol()
screen.addstr(4,2, time.strftime("%a, %d %b %Y %H:%M:%S"))
screen.addstr(4, maxx-len(foo)-2,"{}".format(foo))
screen.hline(5,0,curses.ACS_HLINE,maxx)
screen.move(6,0)
screen.clrtoeol()
screen.addstr(6,0,"{}".format("Resize" if te.is_set() else ""))
screen.refresh()
te.wait(timeout=1)
# time.sleep(1)
te.clear()
screen = curses.initscr()
curses.noecho()
curses.curs_set(0)
screen.keypad(True)
screen.addstr("This is a Sample Curses Script\n\n")
screen.refresh()
te = threading.Event()
clock = threading.Thread(target=update_time, args=(screen,te,))
clock.daemon = True
clock.start()
while 1:
event = screen.getch()
if event == ord("q"):
break
if event == curses.KEY_RESIZE:
te.set()
curses.nocbreak()
screen.keypad(False)
curses.echo()
curses.endwin()
|
Expected behaviour: A resize of the terminal window repaints the display
With time.sleep(1) ist works mostly. But If I use thread_event.wait(timeout=1) I get garbage, when I resize the terminal window a few times.
What am I doing wrong? And how can I get it stable? |
|
Back to top |
|
|
wjb l33t
Joined: 10 Jul 2005 Posts: 608 Location: Fife, Scotland
|
Posted: Wed Jul 19, 2017 10:04 pm Post subject: |
|
|
Is curses threadsafe? - it's acting not.
The thread logic as it is, is not good. It clears the event at the end of the loop, then tests it at the top of the loop - i.e. straight after its been cleared. If you want to know whether it timed out or was signalled you need to test the return value if the wait.
I wouldn't assume its going to be happy about the screen variable being used by the two threads. Possibly use a mutex around rhw blocks of references to screen to stop simultaneous access. Or move all the GUI code into the main thread and do non-GUI work in the other thread(s). |
|
Back to top |
|
|
Hu Moderator
Joined: 06 Mar 2007 Posts: 21633
|
Posted: Thu Jul 20, 2017 1:10 am Post subject: Re: Python: curses and refresh on resize inside a thread |
|
|
As wjb says, that event management looks questionable. You should clear the event immediately after discovering it set, then do the required work. That produces two possible orderings:
- Thread update_time detects the event is set.
- Thread update_time clears the event.
- Thread main sets the event again.
- Thread update_time detects the event is set.
- Thread main sets the event again.
- Thread update_time clears the event. The second set is lost, but the redraw is already queued.
With your current implementation, you can get a bad ordering:- Thread update_time detects the event is set.
- Thread update_time reads the window dimensions and redraws accordingly.
- Thread update_time sleeps.
- The user resizes the window. Thread main sets the event.
- Thread update_time wakes and clears the event.
- The loop starts again, and the event is clear, even though the user resized the window after the last redraw, so another redraw is needed.
As part of that change, you need to avoid rereading the event state. You should create a local variable initialized according to whether the event was set, then use that variable for later tests (such as your adding of the "Resize" string).
If you need more help, please describe exactly what goes wrong. What is the nature of the garbage? |
|
Back to top |
|
|
musv Advocate
Joined: 01 Dec 2002 Posts: 3337 Location: de
|
Posted: Thu Jul 20, 2017 12:32 pm Post subject: |
|
|
Thanks, got it:
Code: | def update_time(screen,te):
while 1:
maxy,maxx = screen.getmaxyx()
foo = "x: {} - y: {}".format(maxx,maxy)
if te.is_set():
te.clear()
curses.resizeterm(maxy,maxx)
screen.clear()
screen.hline(3,0,curses.ACS_HLINE,maxx)
screen.move(4,0)
screen.clrtoeol()
screen.addstr(4,2, time.strftime("%a, %d %b %Y %H:%M:%S"))
screen.addstr(4, maxx-len(foo)-2,"{}".format(foo))
screen.hline(5,0,curses.ACS_HLINE,maxx)
screen.move(6,0)
screen.clrtoeol()
screen.addstr(6,0,"{}".format("Resize" if te.is_set() else ""))
screen.refresh()
te.wait(timeout=10) |
works perfectly. I moved the event clearing into the if-clause.
wjb wrote: | I wouldn't assume its going to be happy about the screen variable being used by the two threads. |
Yes, I'll change that too.
Thanks a lot for your ideas. |
|
Back to top |
|
|
Hu Moderator
Joined: 06 Mar 2007 Posts: 21633
|
Posted: Fri Jul 21, 2017 1:18 am Post subject: |
|
|
I suggested the local variable specifically to prevent the bug you show introduced in the latest version. You left unchanged the ternary expression that chooses either "Resize" or "", but now that the event is cleared inside the conditional block, you will only ever get the string "Resize" if you hit a very tight race where the window is resized a second time after the clear, but before the screen.addstr call. |
|
Back to top |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
|