Python中的多线程curses输出

wca*_*art 3 python curses multithreading

我正在尝试在长期运行的函数的进度条下方实现一个简单的微调器(使用根据此答案改编的代码)。

[########         ] x%
/ Compressing filename
Run Code Online (Sandbox Code Playgroud)

我让压缩和进度条在脚本的主线程中运行,而旋转器在另一个线程中运行,因此它实际上可以在压缩发生时旋转。但是,我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()
Run Code Online (Sandbox Code Playgroud)

这是进度条的伪代码:

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()
Run Code Online (Sandbox Code Playgroud)

并从类似的地方打电话main()

spinner.start()
for each file:
  update_progress_bar
  compress(file)
spinner.stop()
Run Code Online (Sandbox Code Playgroud)

为什么输出有时会被损坏?是因为单独的线程吗?如果是这样,有什么更好的设计方法建议吗?

aba*_*ert 5

cursesPython 模块依赖的库不是curses线程安全的。

\n\n

ncurses有一个curs_threads功能,显然从大约十年前的 5.7 版本就已经存在了。但它需要改变一些 API 调用的方式,并链接到-lncursest,这仍然不是微不足道的,并且 \xe2\x80\xa6 几乎没有人使用过它。

\n\n

据我所知,没有标准安装程序或发行版包会构建 Pythoncurses来链接ncursest\xe2\x80\x94,即使发行版ncursest首先包含(它们通常不会)。即使他们这样做了,也没有线程安全函数的绑定,因此您仍然无法安全地访问诸如设置制表符大小之类的内容。

\n\n
\n\n

根据我的(可能过时的,也可能是平台限制的)经验,你仍然可以逃脱惩罚,但你需要:

\n\n
    \n
  • 显然只有一个线程可以调用诸如getch和 之类的东西getmouse
  • \n
  • 添加一个全局Lock,然后确保每批更新都以 结尾refresh,并且整批更新都在锁内。
  • \n
  • 避免使用 \xe2\x80\x94 中提到的功能的 Python 包装器curs_threads,例如,不要更改 escdelay 或 tabsize。
  • \n
  • 在启动(退出后)其他线程之前,从主线程初始化(并关闭)屏幕。
  • \n
  • 如果可能的话,请确保您还在主线程中创建了所需的所有窗口。(希望您不需要任何动态弹出子窗口或任何东西\xe2\x80\xa6)
  • \n
\n\n
\n\n

但执行此操作的安全方法是执行与tkinter或其他不理解线程的 GUI 库相同的操作。它并不完全相同,但想法相似。最简单的版本是:

\n\n
    \n
  • 将主线程的工作移至另一个后台线程。
  • \n
  • 添加一个queue.Queue,以便您的后台线程可以请求curses运行命令。(你不需要任何复杂的东西来表示“命令”,它只是一个(func, *args)元组,因为 Python。)
  • \n
  • 使主线程围绕从队列中弹出命令并调用它们进行循环。
  • \n
\n\n

如果您的后台线程需要调用返回值的函数,显然您需要使这稍微复杂一些。你可以看看如何multiprocessing.dummy.AsyncResultconcurrent.futures.Future工作。或者你甚至可以Future为了自己的目的而偷窃。但您可能不需要任何那么复杂的东西。

\n\n

如果您正在循环输入,您可能还希望主线程执行此操作(这意味着选择“帧速率”并在等待队列和输入之间交替,并超时)并分派它,即使您总是分派到同一个线程。

\n\n

您甚至可以编写一个mtTkinter-style 包装器来重现curses接口(甚至猴子补丁curses模块),但用将函数和参数放入队列的调用来替换每个函数。但我不确定这是否值得付出努力。

\n