我正在尝试在进度条下方实现一个简单的微调器(使用从this answer改编的代码)以实现长时间运行的功能。
[######## ] x%
/ Compressing filename
我在脚本的主线程中运行了压缩和进度条,在另一个线程中运行了微调器,因此它实际上可以在进行压缩时旋转。但是,我在进度条和微调器中都使用了curses
,并且都在使用curses.refresh()
有时终端会随机输出乱码,我不确定为什么。我认为这是由于微调器的多线程特性,当我禁用微调器时,问题就消失了。
这是微调器的伪代码:
def start(self):
self.busy = True
global stdscr
stdscr = curses.initscr()
curses.noecho()
curses.cbreak()
threading.Thread(target=self.spinner_task).start()
def spinner_task(self):
while self.busy:
stdscr.addstr(1, 0, next(self.spinner_generator))
time.sleep(self.delay)
stdscr.refresh()
这是进度条的伪代码:
progress_bar = "\r[{}] {:.0f}%".format("#" * block + " " * (bar_length - block), round(progress * 100, 0))
progress_file = " {} {}".format(s, filename)
stdscr.clrtoeol()
stdscr.addstr(1, 1, " ")
stdscr.clrtoeol()
stdscr.addstr(0, 0, progress_bar)
stdscr.addstr(1, 1, progress_file)
stdscr.refresh()
从main()
进行调用,例如:
spinner.start()
for each file:
update_progress_bar
compress(file)
spinner.stop()
为什么有时输出会损坏?是因为线程不同吗?如果是这样,有什么更好的设计方法的建议吗?
答案 0 :(得分:2)
Python的curses
模块所依赖的curses
库不是线程安全的。
ncurses
具有curs_threads
功能,该功能自大约十年前的5.7开始就已经存在。但是,这需要更改执行一些API调用的方式,并与-lncursest
进行链接,但这仍然不是一件容易的事,而且…几乎没有人使用过它。
据我所知,没有标准的安装程序或发行版程序包会构建Python curses
来链接ncursest
-即使发行版首先包含ncursest
,他们通常也赢得了胜利没错即使这样做,线程安全功能也没有绑定,所以您 still 仍然无法安全地访问诸如设置tabsize之类的内容。
根据我的经验(可能是过时的,并且可能是平台受限的),尽管如此,您还是可以放手一搏,但是您需要:
getch
和getmouse
之类的东西。Lock
,然后确保每一批更新都以refresh
结尾,并且整批更新都在Lock内部。curs_threads
中提到的功能的Python包装器,例如,请勿更改escdelay或tabsize。但是 safe 的方法是执行与tkinter或其他不了解线程的GUI库相同的操作。这并不完全相同,但是想法是相似的。最简单的版本是:
queue.Queue
,以便您的后台线程可以要求运行curses
命令。 (您不需要任何复杂的东西来表示“命令”,它只是一个(func, *args)
元组,因为Python。)如果您的后台线程需要调用返回值的函数,那么显然您需要使其稍微复杂一些。您可以查看multiprocessing.dummy.AsyncResult
和concurrent.futures.Future
的工作方式。或者,您甚至可以出于自己的目的窃取Future
。但是您可能不需要任何复杂的东西。
如果要遍历输入,您可能还希望主线程执行此操作(这意味着选择“帧速率”,并在等待队列和输入之间进行超时切换)并分派它,即使您总是分派到同一线程。
您甚至可以编写一个mtTkinter
样式的包装程序,该包装程序可以重现curses
接口(甚至可以猴子修补curses
模块),但是用调用将每个函数替换为放置该函数和args在队列中。但是我不确定这样做是否值得。
答案 1 :(得分:0)
如果这是您使用curses
模块的 only 场所,最好的解决方案是停止使用它。
您在此真正使用的curses
的唯一功能是其清除屏幕和移动光标的能力。通过直接输出适当的控制序列,例如
sys.stdout.write("\x1b[f\x1b[J" + progress_bar + "\n" + progress_file)
\x1b[f
序列将光标移至1,1,\x1b[J
清除了从光标位置到屏幕末端的所有内容。
完成屏幕后,无需其他呼叫即可刷新屏幕或重置屏幕。如果需要,您可以再次输出"\x1b[f\x1b[J"
来清除屏幕。
诚然,此方法假设用户使用的是VT100兼容终端。但是,不执行此标准的终端实际上已经灭绝,因此这可能是一个安全的假设。