2024年7月

由于一些特殊原因,计划从阿里云上把ECS服务器的相关资源资源迁移到华为云上,为了保险起见,先申请一个月的华为云ECS服务器进行测试,首先就是搭建FTP服务器进行文件的上传处理,在使用FileZilla Server搭建服务器的时候,出现一些设置上的问题,爬坑了一小段时间才顺利跳出,顺便总结分享一下。

在完成FileZilla Server搭建服务端后,FileZilla 客户端能够成功登录但就是无法列出目录,当然也就无法上传文件,检查很久才发现是安全组设置问题。

1、在ECS服务器端进行FileZilla Server服务端的安装和设置

安装后启用默认的设置,并创建一个用户,指定FTP上传的目录即可,如下所示,用户访问支持读写操作即可。

然后设置下防火墙,允许FileZilla Server应用通过防火墙。

添加应用,选择FileZilla Server的安装目录,选择运行文件即可。

完成这个步骤后,由于ECS会在安全组进行统一的入口控制处理,还需要进行安全组的设置处理。

2、对ECS服务器端的安全组进行设置

一般来说,FTP默认的端口是21,我们指定一个端口,让它进来即可,如下所示。

默认一般也就是这样操作,然后使用FileZilla客户端进行测试登录FTP服务,发现登录成功,但就是无法列出目录,如下所示。

排查很多原因,都没有找到合适的答案,最后在一片文章总看到,可能是端口开放的还不够。

检查下阿里云里面的FTP端口,因为上面我是工作正常的,发现原来是我对入口的端口全部放开的。

为了验证这个是否端口开放不够导致的,我根据上面的端口提示,开放了指定的端口,在华为云上增加相应的端口入口开放,如下所示。

同时,在FTP的服务端,我指定了FileZilla的被动模式可能占用的端口设置一下,这样就对应我的安全组设置端口了。

再次使用FTP客户端进行连接测试,快速的登录并能够列出目录了。

这样就完成了FTP的服务端和客户端的顺利对接,剩下的就是使用FTP来搬东西了。

上面就是一些简单的FTP设置处理,对于新申请服务器或者更换服务器,搭建FTP服务端的可能有些帮助,自己记录下来也方便自己下次跳过此坑。

本文主要记录 Linux Cgroup V2 版本基本使用操作,包括 cpu、memory 子系统演示。

1. 开启 Cgroup V2

版本检查

通过下面这条命令来查看当前系统使用的 Cgroups V1 还是 V2

stat -fc %T /sys/fs/cgroup/

如果输出是
cgroup2fs
那就是 V2,就像这样

root@tezn:~# stat -fc %T /sys/fs/cgroup/
cgroup2fs

如果输出是
tmpfs
那就是 V1,就像这样

[root@docker cgroup]# stat -fc %T /sys/fs/cgroup/
tmpfs

启用 cgroup v2

如果当前系统未启用 Cgroup V2,也可以通过修改内核 cmdline 引导参数在你的 Linux 发行版上手动启用 cgroup v2。

如果你的发行版使用 GRUB,则应在
/etc/default/grub
下的
GRUB_CMDLINE_LINUX
中添加
systemd.unified_cgroup_hierarchy=1
, 然后执行
sudo update-grub

具体如下:

1)编辑 grub 配置

vi /etc/default/grub

内容大概是这样的:

GRUB_DEFAULT=0
GRUB_TIMEOUT_STYLE=hidden
GRUB_TIMEOUT=0
GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian`
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"
GRUB_CMDLINE_LINUX=""

对最后一行
GRUB_CMDLINE_LINUX
进行修改

GRUB_CMDLINE_LINUX="quiet splash systemd.unified_cgroup_hierarchy=1"

2)然后执行以下命令更新 GRUB 配置

sudo update-grub

3)最后查看一下启动参数,确认配置修改上了

cat /boot/grub/grub.cfg | grep "systemd.unified_cgroup_hierarchy=1"

4)然后就是重启

reboot

重启后查看,不出意外切换到 cgroups v2 了

root@cgroupv2:~# stat -fc %T /sys/fs/cgroup/
cgroup2fs

发行版推荐

不过,推荐的方法仍是使用一个默认已启用 cgroup v2 的发行版。

有关使用 cgroup v2 的 Linux 发行版的列表,

  • Container-Optimized OS(从 M97 开始)
  • Ubuntu(从 21.10 开始,推荐 22.04+)
  • Debian GNU/Linux(从 Debian 11 Bullseye 开始)
  • Fedora(从 31 开始)
  • Arch Linux(从 2021 年 4 月开始)
  • RHEL 和类似 RHEL 的发行版(从 9 开始)

2. 基本使用

cgroup v2 使用上和 v1 版本基本一致,v2 版本也是默认在
/sys/fs/cgroup/
目录。

root@mydocker:~# ls /sys/fs/cgroup/
cgroup.controllers      cgroup.subtree_control  init.scope       system.slice
cgroup.max.depth        cgroup.threads          io.cost.model    user.slice
cgroup.max.descendants  cpu.pressure            io.cost.qos
cgroup.procs            cpuset.cpus.effective   io.pressure
cgroup.stat             cpuset.mems.effective   memory.pressure
  • 创建 sub-cgroup: 只需要创建一个子目录
cd /sys/fs/cgroup
mkdir $CGROUP_NAME
  • 将进程移动到指定 cgroup:将 PID 写入到相应 cgroup 的 cgroup.procs 文件即可,就像这样:
echo 1001 > /sys/fs/cgroup/test/cgroup.procs
  • 删除 cgroup/sub-cgroup: 也是直接删除对应目录即可
    • 如果一个cgroup 已经没有任何children或活进程,那直接删除对应的文件夹就删除该cgroup了
    • 如果一个cgroup已经没有children,但是还有僵尸进程,也认为这个cgroup是空的,可以直接删除
rmdir /sys/fs/cgroup/test
  • 修改 cpu、memory 限制:往对应配置文件写入配置内容即可
    • cpu.max 用于配置 cpu 使用限制
    • memory.max 则用于配置 内存使用限制
    • ...

创建 cgroup

接下来,以 cpu、memory 为例,简单演示一下 cgroup v2 版本使用

root@mydocker:~# cd /sys/fs/cgroup/
root@mydocker:/sys/fs/cgroup# mkdir test
root@mydocker:/sys/fs/cgroup# cd test
root@mydocker:/sys/fs/cgroup/test# ls
cgroup.controllers      cpu.uclamp.max         memory.current
cgroup.events           cpu.uclamp.min         memory.events
cgroup.freeze           cpu.weight             memory.events.local
cgroup.max.depth        cpu.weight.nice        memory.high
cgroup.max.descendants  cpuset.cpus            memory.low
cgroup.procs            cpuset.cpus.effective  memory.max
cgroup.stat             cpuset.cpus.partition  memory.min
cgroup.subtree_control  cpuset.mems            memory.oom.group
cgroup.threads          cpuset.mems.effective  memory.pressure
cgroup.type             io.max                 memory.stat
cpu.max                 io.pressure            pids.current
cpu.pressure            io.stat                pids.events
cpu.stat                io.weight              pids.max

CPU

启动一个死循环

root@mydocker:/sys/fs/cgroup/test# while : ; do : ; done &
[1] 90482

不出意外的话,应该占用了 100% cpu

PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND    
90482 root      20   0   10160   1772      0 R  99.3   0.1   0:05.01 bash 

接下来使用 cgroup v2 限制该进程只能使用 20% cpu

1)修改配置

echo 2000 10000 > cpu.max

含义是在 10000 微秒的 CPU 时间周期内,有 2000 微秒是分配给本 cgroup 的,也就是本 cgroup 管理的进程在单核 CPU 上的使用率不会超过 20%。

2)将进程加入当前 cgroup

echo 90482 > cgroup.procs

再次查看

PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND    
90482 root      20   0   10160   1772      0 R  20.2   0.1   2:07.78 bash 

可以看到,已经被限制到了 20%

Memory

接下来演示内存限制,使用以下代码来模拟内存消耗,

cat <<EOF > ~/mem-allocate.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define MB (1024 * 1024)

int main(int argc, char *argv[])
{
    char *p;
    int i = 0;
    while(1) {
        p = (char *)malloc(MB);
        memset(p, 0, MB);
        printf("%dM memory allocated\n", ++i);
        sleep(1);
    }

    return 0;
}
EOF

编译

gcc ~/mem-allocate.c -o ~/mem-allocate

然后启动该文件

root@mydocker:/sys/fs/cgroup/test# ~/mem-allocate
1M memory allocated
2M memory allocated
3M memory allocated
4M memory allocated
5M memory allocated
6M memory allocated
7M memory allocated
8M memory allocated
9M memory allocated
10M memory allocated
11M memory allocated
12M memory allocated
^C

可以看到,每秒会消耗 1M 内存,若不停止会一直运行直到 OOM。

接下来使用 cgroup v2 限制最多消耗 10M 内存。

1)修改配置

单位为字节 10485760= 10 * 1024 * 1024

echo 10485760 > memory.max

也就是本 cgroup 管理的进程内存使用不会超过 10M

2)将进程加入当前 cgroup

#将当前bash加入到test中,这样这个bash创建的所有进程都会自动加入到test中
sh -c "echo $$ >> cgroup.procs"

再次查看

root@mydocker:/sys/fs/cgroup/test# ~/mem-allocate
1M memory allocated
2M memory allocated
3M memory allocated
4M memory allocated
5M memory allocated
6M memory allocated
7M memory allocated
8M memory allocated
9M memory allocated
Killed

可以看到,到 10M 时就因为达到内存上限而被 Kill 了。

删除 cgroup

演示完成,把 cgroup 删除。

首先把进程 kill 一下

root@mydocker:/sys/fs/cgroup# cat test/cgroup.procs 
90444
90630
root@mydocker:/sys/fs/cgroup# kill -9 90630
root@mydocker:/sys/fs/cgroup# kill -9 90444

然后删除目录

root@mydocker:/sys/fs/cgroup# rmdir test

这样 cgroup 就删除了。

3. v1 v2 对比

v1 的 cgroup 为每个控制器都使用独立的树(目录)

[root@docker cgroup]# ls /sys/fs/cgroup/
blkio  cpu  cpuacct  cpuacct,cpu  cpu,cpuacct  cpuset  devices  freezer  hugetlb  memory  net_cls  net_cls,net_prio  net_prio  perf_event  pids  rdma  systemd

每个目录就代表了一个 cgroup subsystem,比如要限制 cpu 则需要到 cpu 目录下创建子目录(树),限制 memory 则需要到 memory 目录下去创建子目录(树)。

比如 Docker 就会在 cpu、memory 等等目录下都创建一个名为 docker 的目录,在 docker 目录下在根据 containerID 创建子目录来实现资源限制。

各个 Subsystem 各自为政,看起来比混乱,难以管理

因此最终的结果就是:

  1. 用户空间最后
    管理着多个非常类似的 hierarchy
  2. 在执行 hierarchy 管理操作时,
    每个 hierarchy 上都重复着相同的操作

v2 中对 cgroups 的最大更改是将重点放在简化层次结构上

  • v1 为每个控制器使用独立的树(例如
    /sys/fs/cgroup/cpu/GROUPNAME

    /sys/fs/cgroup/memory/GROUPNAME
    )。
  • v2 将统一
    /sys/fs/cgroup/GROUPNAME
    中的树,如果进程 X 加入
    /sys/fs/cgroup/test
    ,则启用 test 的每个控制器都将控制进程 X。

更多 v1 和 v2 差异见
v1
存在的问题及
v2
的设计考虑


【从零开始写 Docker 系列】
持续更新中,搜索公众号【
探索云原生
】订阅,阅读更多文章。


4. 小结

本文主要分享了 Linux cgroup v2 版本的基本使用,以及 v1 和 v2 版本的差异。

更多 cgroup v2 信息推荐阅读:
Control Group v2
及其译文
Control Group v2(cgroupv2 权威指南)(KernelDoc, 2021)

这是一个自己写的使用pyqt5制作简单计分桌面应用的实例,希望对大家有所帮助。制作这个小程序的起因是因为有个艺术类比赛需要设计这个一个桌面程序,方便统分。

(此程序尚存在部分小bug,请慎用,公开代码只为让小白熟悉如何开发一个简单的桌面计分程序)

ui设计

众所周知,使用pyqt5开发可以直接使用designer来设计界面,所以,我将页面代码直接贴给大家,具体效果截图展示。

比赛准备阶段界面

image-20240711143434332

比赛进行中界面

image-20240711143507616

完整ui代码

为了方便,并没有转成.py形式的文件

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Form</class>
 <widget class="QWidget" name="Form">
  <property name="enabled">
   <bool>true</bool>
  </property>
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>1920</width>
    <height>1080</height>
   </rect>
  </property>
  <property name="minimumSize">
   <size>
    <width>1920</width>
    <height>1080</height>
   </size>
  </property>
  <property name="maximumSize">
   <size>
    <width>1920</width>
    <height>1080</height>
   </size>
  </property>
  <property name="windowTitle">
   <string>Form</string>
  </property>
  <widget class="QWidget" name="race_prepare_page" native="true">
   <property name="enabled">
    <bool>true</bool>
   </property>
   <property name="geometry">
    <rect>
     <x>440</x>
     <y>130</y>
     <width>1211</width>
     <height>721</height>
    </rect>
   </property>
   <property name="styleSheet">
    <string notr="true"/>
   </property>
   <widget class="QTableWidget" name="score_table_display">
    <property name="enabled">
     <bool>true</bool>
    </property>
    <property name="geometry">
     <rect>
      <x>580</x>
      <y>190</y>
      <width>621</width>
      <height>341</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px ;</string>
    </property>
   </widget>
   <widget class="QLabel" name="score_display_label">
    <property name="geometry">
     <rect>
      <x>830</x>
      <y>540</y>
      <width>131</width>
      <height>41</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px &quot;Adobe Arabic&quot;;</string>
    </property>
    <property name="text">
     <string>选手信息展示</string>
    </property>
   </widget>
   <widget class="QPushButton" name="race_info_button">
    <property name="geometry">
     <rect>
      <x>200</x>
      <y>660</y>
      <width>121</width>
      <height>51</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px &quot;Adobe Arabic&quot;;</string>
    </property>
    <property name="text">
     <string>确认信息</string>
    </property>
   </widget>
   <widget class="QPushButton" name="race_start_button">
    <property name="geometry">
     <rect>
      <x>740</x>
      <y>90</y>
      <width>121</width>
      <height>51</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px &quot;Adobe Arabic&quot;;</string>
    </property>
    <property name="text">
     <string>比赛开始</string>
    </property>
   </widget>
   <widget class="QLabel" name="race_preparation_label">
    <property name="geometry">
     <rect>
      <x>510</x>
      <y>10</y>
      <width>181</width>
      <height>41</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 30px &quot;Adobe Arabic&quot;;</string>
    </property>
    <property name="text">
     <string>比赛准备阶段</string>
    </property>
   </widget>
   <widget class="QLabel" name="music_name_label">
    <property name="enabled">
     <bool>true</bool>
    </property>
    <property name="geometry">
     <rect>
      <x>10</x>
      <y>350</y>
      <width>91</width>
      <height>81</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px &quot;Adobe Arabic&quot;;</string>
    </property>
    <property name="text">
     <string>请按选手排序输入曲名名称</string>
    </property>
    <property name="wordWrap">
     <bool>true</bool>
    </property>
   </widget>
   <widget class="QRadioButton" name="remove_yes">
    <property name="geometry">
     <rect>
      <x>250</x>
      <y>560</y>
      <width>61</width>
      <height>21</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px &quot;Adobe Arabic&quot;;</string>
    </property>
    <property name="text">
     <string>是</string>
    </property>
   </widget>
   <widget class="QLineEdit" name="judge_number_input">
    <property name="geometry">
     <rect>
      <x>100</x>
      <y>110</y>
      <width>151</width>
      <height>31</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px ;</string>
    </property>
    <property name="text">
     <string/>
    </property>
    <property name="placeholderText">
     <string>填写示例:1</string>
    </property>
   </widget>
   <widget class="QLabel" name="race_name_label">
    <property name="geometry">
     <rect>
      <x>10</x>
      <y>60</y>
      <width>91</width>
      <height>24</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px &quot;Adobe Arabic&quot;;</string>
    </property>
    <property name="text">
     <string>比赛名称</string>
    </property>
   </widget>
   <widget class="QTextEdit" name="judge_namelist_input">
    <property name="geometry">
     <rect>
      <x>100</x>
      <y>160</y>
      <width>151</width>
      <height>171</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px;</string>
    </property>
    <property name="markdown">
     <string/>
    </property>
    <property name="html">
     <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'SimSun'; font-size:20px; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
    </property>
    <property name="placeholderText">
     <string>填写示例:   张三                 李四</string>
    </property>
   </widget>
   <widget class="QLabel" name="judge_number_label">
    <property name="geometry">
     <rect>
      <x>11</x>
      <y>116</y>
      <width>81</width>
      <height>24</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px &quot;Adobe Arabic&quot;;</string>
    </property>
    <property name="text">
     <string>评委数量</string>
    </property>
   </widget>
   <widget class="QTextEdit" name="player_namelist_input">
    <property name="geometry">
     <rect>
      <x>390</x>
      <y>160</y>
      <width>151</width>
      <height>171</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px;</string>
    </property>
    <property name="markdown">
     <string/>
    </property>
    <property name="html">
     <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'SimSun'; font-size:20px; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
    </property>
    <property name="placeholderText">
     <string>填写示例:   张三               李四</string>
    </property>
   </widget>
   <widget class="QLabel" name="player_name_label">
    <property name="geometry">
     <rect>
      <x>300</x>
      <y>160</y>
      <width>91</width>
      <height>81</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px &quot;Adobe Arabic&quot;;</string>
    </property>
    <property name="text">
     <string>请按选手排序输入姓名</string>
    </property>
    <property name="wordWrap">
     <bool>true</bool>
    </property>
   </widget>
   <widget class="QLineEdit" name="race_name_input">
    <property name="geometry">
     <rect>
      <x>100</x>
      <y>54</y>
      <width>441</width>
      <height>31</height>
     </rect>
    </property>
    <property name="accessibleName">
     <string/>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px;</string>
    </property>
    <property name="inputMask">
     <string/>
    </property>
    <property name="text">
     <string/>
    </property>
    <property name="placeholderText">
     <string>默认比赛</string>
    </property>
   </widget>
   <widget class="QLabel" name="judge_name_label">
    <property name="geometry">
     <rect>
      <x>10</x>
      <y>160</y>
      <width>91</width>
      <height>81</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px &quot;Adobe Arabic&quot;;</string>
    </property>
    <property name="text">
     <string>请按评委排序输入姓名</string>
    </property>
    <property name="wordWrap">
     <bool>true</bool>
    </property>
   </widget>
   <widget class="QLineEdit" name="player_number_input">
    <property name="geometry">
     <rect>
      <x>390</x>
      <y>110</y>
      <width>151</width>
      <height>31</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px ;</string>
    </property>
    <property name="text">
     <string/>
    </property>
    <property name="placeholderText">
     <string>填写示例:1</string>
    </property>
   </widget>
   <widget class="QRadioButton" name="remove_no">
    <property name="geometry">
     <rect>
      <x>340</x>
      <y>560</y>
      <width>51</width>
      <height>21</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px &quot;Adobe Arabic&quot;;</string>
    </property>
    <property name="text">
     <string>否</string>
    </property>
   </widget>
   <widget class="QLabel" name="remove_score_label">
    <property name="geometry">
     <rect>
      <x>10</x>
      <y>614</y>
      <width>221</width>
      <height>24</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px &quot;Adobe Arabic&quot;;</string>
    </property>
    <property name="text">
     <string>去掉几个最高分和最低分</string>
    </property>
   </widget>
   <widget class="QLineEdit" name="remove_score_input">
    <property name="geometry">
     <rect>
      <x>250</x>
      <y>610</y>
      <width>151</width>
      <height>31</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px ;</string>
    </property>
    <property name="text">
     <string/>
    </property>
    <property name="placeholderText">
     <string>填写示例:1</string>
    </property>
   </widget>
   <widget class="QLabel" name="player_number_label">
    <property name="geometry">
     <rect>
      <x>300</x>
      <y>116</y>
      <width>91</width>
      <height>24</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px &quot;Adobe Arabic&quot;;</string>
    </property>
    <property name="text">
     <string>选手数量</string>
    </property>
   </widget>
   <widget class="QLabel" name="remove_choose_label">
    <property name="geometry">
     <rect>
      <x>10</x>
      <y>560</y>
      <width>221</width>
      <height>24</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px &quot;Adobe Arabic&quot;;</string>
    </property>
    <property name="text">
     <string>是否去掉最高分和最低分</string>
    </property>
   </widget>
   <widget class="QTextEdit" name="music_namelist_input">
    <property name="geometry">
     <rect>
      <x>100</x>
      <y>350</y>
      <width>441</width>
      <height>181</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px;</string>
    </property>
    <property name="markdown">
     <string/>
    </property>
    <property name="html">
     <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'SimSun'; font-size:20px; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
    </property>
    <property name="placeholderText">
     <string>填写示例:                                  古典乐                                       爵士乐</string>
    </property>
   </widget>
   <widget class="QPushButton" name="continue_race_button">
    <property name="geometry">
     <rect>
      <x>890</x>
      <y>90</y>
      <width>151</width>
      <height>51</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px &quot;Adobe Arabic&quot;;</string>
    </property>
    <property name="text">
     <string>继续上次比赛</string>
    </property>
   </widget>
   <widget class="QPushButton" name="quit_code_button">
    <property name="geometry">
     <rect>
      <x>1070</x>
      <y>90</y>
      <width>121</width>
      <height>51</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px &quot;Adobe Arabic&quot;;</string>
    </property>
    <property name="text">
     <string>退出程序</string>
    </property>
   </widget>
   <widget class="QPushButton" name="save_info_button">
    <property name="geometry">
     <rect>
      <x>590</x>
      <y>90</y>
      <width>121</width>
      <height>51</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px &quot;Adobe Arabic&quot;;</string>
    </property>
    <property name="text">
     <string>保存信息</string>
    </property>
   </widget>
  </widget>
  <widget class="QWidget" name="race_going_page" native="true">
   <property name="geometry">
    <rect>
     <x>440</x>
     <y>130</y>
     <width>1211</width>
     <height>721</height>
    </rect>
   </property>
   <widget class="QLabel" name="race_going_label">
    <property name="geometry">
     <rect>
      <x>0</x>
      <y>10</y>
      <width>1211</width>
      <height>41</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 30px &quot;Adobe Arabic&quot;;</string>
    </property>
    <property name="text">
     <string>比赛进行阶段</string>
    </property>
    <property name="alignment">
     <set>Qt::AlignCenter</set>
    </property>
   </widget>
   <widget class="QLabel" name="judge_one_label_1">
    <property name="geometry">
     <rect>
      <x>80</x>
      <y>146</y>
      <width>181</width>
      <height>41</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px;</string>
    </property>
    <property name="text">
     <string>姓名:1号评委</string>
    </property>
   </widget>
   <widget class="QLineEdit" name="judgeone_score_input_1">
    <property name="geometry">
     <rect>
      <x>300</x>
      <y>150</y>
      <width>151</width>
      <height>31</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px ;</string>
    </property>
    <property name="text">
     <string/>
    </property>
    <property name="placeholderText">
     <string>填写示例:1</string>
    </property>
   </widget>
   <widget class="QLabel" name="judge_one_label_2">
    <property name="geometry">
     <rect>
      <x>81</x>
      <y>202</y>
      <width>181</width>
      <height>41</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px;</string>
    </property>
    <property name="text">
     <string>姓名:2号评委</string>
    </property>
   </widget>
   <widget class="QLineEdit" name="judgeone_score_input_2">
    <property name="geometry">
     <rect>
      <x>301</x>
      <y>206</y>
      <width>151</width>
      <height>31</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px ;</string>
    </property>
    <property name="text">
     <string/>
    </property>
    <property name="placeholderText">
     <string>填写示例:1</string>
    </property>
   </widget>
   <widget class="QLabel" name="judge_one_label_3">
    <property name="geometry">
     <rect>
      <x>81</x>
      <y>252</y>
      <width>181</width>
      <height>41</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px;</string>
    </property>
    <property name="text">
     <string>姓名:3号评委</string>
    </property>
   </widget>
   <widget class="QLineEdit" name="judgeone_score_input_3">
    <property name="geometry">
     <rect>
      <x>301</x>
      <y>256</y>
      <width>151</width>
      <height>31</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px ;</string>
    </property>
    <property name="text">
     <string/>
    </property>
    <property name="placeholderText">
     <string>填写示例:1</string>
    </property>
   </widget>
   <widget class="QLineEdit" name="judgeone_score_input_4">
    <property name="geometry">
     <rect>
      <x>300</x>
      <y>310</y>
      <width>151</width>
      <height>31</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px ;</string>
    </property>
    <property name="text">
     <string/>
    </property>
    <property name="placeholderText">
     <string>填写示例:1</string>
    </property>
   </widget>
   <widget class="QLabel" name="judge_one_label_4">
    <property name="geometry">
     <rect>
      <x>80</x>
      <y>306</y>
      <width>181</width>
      <height>41</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px;</string>
    </property>
    <property name="text">
     <string>姓名:4号评委</string>
    </property>
   </widget>
   <widget class="QLabel" name="judge_one_label_5">
    <property name="geometry">
     <rect>
      <x>81</x>
      <y>362</y>
      <width>181</width>
      <height>41</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px;</string>
    </property>
    <property name="text">
     <string>姓名:5号评委</string>
    </property>
   </widget>
   <widget class="QLineEdit" name="judgeone_score_input_5">
    <property name="geometry">
     <rect>
      <x>301</x>
      <y>366</y>
      <width>151</width>
      <height>31</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px ;</string>
    </property>
    <property name="text">
     <string/>
    </property>
    <property name="placeholderText">
     <string>填写示例:1</string>
    </property>
   </widget>
   <widget class="QLabel" name="judge_one_label_6">
    <property name="geometry">
     <rect>
      <x>81</x>
      <y>422</y>
      <width>181</width>
      <height>41</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px;</string>
    </property>
    <property name="text">
     <string>姓名:6号评委</string>
    </property>
   </widget>
   <widget class="QLineEdit" name="judgeone_score_input_6">
    <property name="geometry">
     <rect>
      <x>301</x>
      <y>426</y>
      <width>151</width>
      <height>31</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px ;</string>
    </property>
    <property name="text">
     <string/>
    </property>
    <property name="placeholderText">
     <string>填写示例:1</string>
    </property>
   </widget>
   <widget class="QLabel" name="judge_one_label_7">
    <property name="geometry">
     <rect>
      <x>81</x>
      <y>482</y>
      <width>181</width>
      <height>41</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px;</string>
    </property>
    <property name="text">
     <string>姓名:7号评委</string>
    </property>
   </widget>
   <widget class="QLineEdit" name="judgeone_score_input_7">
    <property name="geometry">
     <rect>
      <x>301</x>
      <y>486</y>
      <width>151</width>
      <height>31</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px ;</string>
    </property>
    <property name="text">
     <string/>
    </property>
    <property name="placeholderText">
     <string>填写示例:1</string>
    </property>
   </widget>
   <widget class="QLabel" name="judge_one_label_8">
    <property name="geometry">
     <rect>
      <x>81</x>
      <y>542</y>
      <width>181</width>
      <height>41</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px;</string>
    </property>
    <property name="text">
     <string>姓名:8号评委</string>
    </property>
   </widget>
   <widget class="QLineEdit" name="judgeone_score_input_8">
    <property name="geometry">
     <rect>
      <x>301</x>
      <y>546</y>
      <width>151</width>
      <height>31</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px ;</string>
    </property>
    <property name="text">
     <string/>
    </property>
    <property name="placeholderText">
     <string>填写示例:1</string>
    </property>
   </widget>
   <widget class="QLabel" name="judge_one_label_9">
    <property name="geometry">
     <rect>
      <x>81</x>
      <y>602</y>
      <width>181</width>
      <height>41</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px;</string>
    </property>
    <property name="text">
     <string>姓名:9号评委</string>
    </property>
   </widget>
   <widget class="QLineEdit" name="judgeone_score_input_9">
    <property name="geometry">
     <rect>
      <x>301</x>
      <y>606</y>
      <width>151</width>
      <height>31</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px ;</string>
    </property>
    <property name="text">
     <string/>
    </property>
    <property name="placeholderText">
     <string>填写示例:1</string>
    </property>
   </widget>
   <widget class="QLabel" name="judge_com_label_1">
    <property name="geometry">
     <rect>
      <x>510</x>
      <y>250</y>
      <width>151</width>
      <height>41</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 24px;</string>
    </property>
    <property name="text">
     <string>最高分:</string>
    </property>
   </widget>
   <widget class="QLabel" name="judge_com_label_2">
    <property name="geometry">
     <rect>
      <x>700</x>
      <y>250</y>
      <width>161</width>
      <height>41</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 24px;</string>
    </property>
    <property name="text">
     <string>最低分:</string>
    </property>
   </widget>
   <widget class="QLabel" name="judge_com_label_3">
    <property name="geometry">
     <rect>
      <x>510</x>
      <y>320</y>
      <width>151</width>
      <height>41</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 30px;</string>
    </property>
    <property name="text">
     <string>实际得分:</string>
    </property>
   </widget>
   <widget class="QLabel" name="judge_com_label_4">
    <property name="geometry">
     <rect>
      <x>670</x>
      <y>300</y>
      <width>181</width>
      <height>81</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 60px;</string>
    </property>
    <property name="text">
     <string/>
    </property>
   </widget>
   <widget class="QPushButton" name="next_player_button">
    <property name="geometry">
     <rect>
      <x>630</x>
      <y>170</y>
      <width>151</width>
      <height>51</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px &quot;Adobe Arabic&quot;;</string>
    </property>
    <property name="text">
     <string>下一个选手</string>
    </property>
   </widget>
   <widget class="QPushButton" name="exit_race_button">
    <property name="geometry">
     <rect>
      <x>970</x>
      <y>170</y>
      <width>151</width>
      <height>51</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px &quot;Adobe Arabic&quot;;</string>
    </property>
    <property name="text">
     <string>比赛结束</string>
    </property>
   </widget>
   <widget class="QLabel" name="current_player_label">
    <property name="geometry">
     <rect>
      <x>80</x>
      <y>90</y>
      <width>1091</width>
      <height>41</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 24px;</string>
    </property>
    <property name="text">
     <string>当前选手:</string>
    </property>
   </widget>
   <widget class="QPushButton" name="player_quit_button">
    <property name="geometry">
     <rect>
      <x>800</x>
      <y>170</y>
      <width>151</width>
      <height>51</height>
     </rect>
    </property>
    <property name="styleSheet">
     <string notr="true">font: 20px &quot;Adobe Arabic&quot;;</string>
    </property>
    <property name="text">
     <string>当前选手弃权</string>
    </property>
   </widget>
  </widget>
 </widget>
 <resources/>
 <connections/>
</ui>

代码设计

程序主要功能包含,文件上传,文件读取,分数计算,然后还有一些按钮绑定事件。

from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5 import uic

import keyboard
import sys
import os
import pandas as pd
import docx

def get_path(relative_path):
    try:
        base_path = sys._MEIPASS
    except AttributeError:
        base_path = os.path.abspath(".")

    return os.path.normpath(os.path.join(base_path, relative_path))

def get_decimal_places(num):
    import decimal
    if '.' in str(num):
        num = str(float(num))   #如果传入的是字符串形式的浮点数,先去掉小数后面无意义的0
    d = decimal.Decimal(str(num))
    return abs(d.as_tuple().exponent)

def del_files(path_file):
    ls = os.listdir(path_file)
    for i in ls:
        f_path = os.path.join(path_file, i)
        # 判断是否是一个目录,若是,则递归删除
        if os.path.isdir(f_path):
            del_files(f_path)
        else:
            os.remove(f_path)


class MyWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.ui = uic.loadUi(get_path("assets/comscore_final.ui")) #读取ui文件
        # 展示窗口
        self.ui.setWindowTitle('比赛计分系统')

        self.ui.setWindowFlag(Qt.WindowCloseButtonHint, False)
        
        #提取每个要使用的控件 
        #比赛准备阶段  
        self.race_prepare=self.ui.race_prepare_page #比赛准备页面

        self.race_name=self.ui.race_name_input #比赛名称

        self.judge_number=self.ui.judge_number_input #评委数量
        self.judge_namelist=self.ui.judge_namelist_input #评委信息

        self.player_number=self.ui.player_number_input #选手数量
        self.player_namelist=self.ui.player_namelist_input #选手信息

        self.music_namelist=self.ui.music_namelist_input #音乐信息

        self.race_info=self.ui.race_info_button #确认信息按钮

        self.continue_race=self.ui.continue_race_button #继续上次比赛按钮
        self.race_start=self.ui.race_start_button #比赛开始按钮'
        self.remove_score=self.ui.remove_score_input 
        self.save_info=self.ui.save_info_button #保存信息
        self.quit_code=self.ui.quit_code_button #退出程序

        self.race_start.setEnabled(False)
        self.save_info.setEnabled(False)

        #是否去掉最高分和最低分
        self.remove_yes_radiobox=self.ui.remove_yes
        self.remove_no_radiobox=self.ui.remove_no

        #去掉最高分和最低分的数量
        self.remove_score_label_now=self.ui.remove_score_label
        self.remove_score=self.ui.remove_score_input 

        #默认选择不去掉,并且设置不显示去掉几个最高分和最低分的框
        self.remove_no_radiobox.setChecked(True)
        self.remove_score.setVisible(False) #初始设置隐藏
        self.remove_score_label_now.setVisible(False) #初始设置隐藏

        #toggled信号与槽函数绑定
        self.remove_yes_radiobox.toggled.connect(lambda :self.func_radiobox_btnstate(self.remove_yes_radiobox))
        self.remove_no_radiobox.toggled.connect(lambda :self.func_radiobox_btnstate(self.remove_no_radiobox))

        #选手信息展示
        self.score_table=self.ui.score_table_display #选手信息展示

        # 绑定信号与槽函数
        self.race_info.clicked.connect(self.func_race_info) #信息导入
        self.continue_race.clicked.connect(self.func_continue_race)# 根据文件来读取信息
        self.race_start.clicked.connect(self.func_race_start) #比赛开始按钮
        self.save_info.clicked.connect(self.func_save_info) #保存信息
        self.quit_code.clicked.connect(self.func_quit_code)


        #选手信息展示区域函数
        self.score_table.setSelectionMode(QAbstractItemView.SingleSelection)#设置选取方式为单个选取
        self.score_table.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn) #设置水平滚动
        self.score_table.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)  #设置竖直滚动

        #---------------------------------------------------------------------------------------------------------------------
        #---------------------------------------------------------------------------------------------------------------------

        #比赛进行阶段
        self.race_going=self.ui.race_going_page #比赛进行页面
        self.race_going.setVisible(False) #初始设置隐藏

        self.race_going_name=self.ui.race_going_label

        #当前选手
        self.current_player=self.ui.current_player_label 

        #评委列表
        self.judge_one_1=self.ui.judge_one_label_1
        self.judgeone_score_1=self.ui.judgeone_score_input_1

        #设置校验规则,实例化浮点校验器,并设置范围0~10,精度为小数点两位
        myValidator = QRegExpValidator(QRegExp("^(10|\d(\.\d{1,2})?)$"))

        #初始设置隐藏
        for show_i in range(1,10):
            judge_one_label_name="judge_one_label_"+str(show_i)
            judge_one_temp = self.race_going.findChild(QLabel,judge_one_label_name)
            judge_one_temp.setVisible(False)

            judgeone_score_input_name="judgeone_score_input_"+str(show_i)
            judgeone_score_temp = self.race_going.findChild(QLineEdit,judgeone_score_input_name)
            judgeone_score_temp.setVisible(False)
            #设置校验规则,限制只能输入数字还有小数后两位
            judgeone_score_temp.returnPressed.connect(self.func_text_change)
            judgeone_score_temp.setValidator(myValidator)
        
        #最高分,最低分,实际得分
        self.judge_com_max=self.ui.judge_com_label_1  #最高分
        self.judge_com_min=self.ui.judge_com_label_2  #最低分
        self.judge_com_average_label=self.ui.judge_com_label_3  #实际得分标签
        self.judge_com_average=self.ui.judge_com_label_4 #分数
        for show_i in range(1,5):
            judge_com_label_name="judge_com_label_"+str(show_i)
            judge_com_temp = self.race_going.findChild(QLabel,judge_com_label_name)
            judge_com_temp.setVisible(False)

        #计算总分按钮
        # self.com_score=self.ui.com_score_button 

        #下一个选手按钮
        self.next_player=self.ui.next_player_button 

        #比赛结束按钮
        self.exit_race=self.ui.exit_race_button 

        #选手弃权按钮
        self.player_quit=self.ui.player_quit_button


        # 绑定信号与槽函数
        # self.com_score.clicked.connect(self.func_com_score) #计算总分
        self.next_player.clicked.connect(self.func_next_player) #下一个选手
        self.exit_race.clicked.connect(self.func_exit_race) #结束比赛
        self.player_quit.clicked.connect(self.func_player_quit) #选手弃权
        
        #赋予一个全局使用的数据变量
        self.all_data_df=[]
        self.have_score_number=0 #表示当前已经计分的选手数量
        self.remove_score_number=0

    def func_save_info(self):
        reply_one=self.setQMessageBoxtwoButtonTextENToCN('提示', '是否保存信息?',QMessageBox.Question)
        if reply_one == QMessageBox.Yes: 
            #先将读取的数据保存起来,以tablewight数据表信息为准
            player_index=[]
            player_name=[]
            music_name=[]
            for qtable_row in range(int(self.player_number.text())):
                player_index.append(self.score_table.item(qtable_row,0).text())  #获取某行某列item中的x信息
                player_name.append(self.score_table.item(qtable_row,1).text())  #获取某行某列item中的x信息
                music_name.append(self.score_table.item(qtable_row,2).text())  #获取某行某列item中的x信息
            
            judge_all_list=[]
            for qtable_column in range(int(self.judge_number.text())):
                judge_one_list=[' ']*int(self.player_number.text()) #将列数据全部填充为空格
                judge_all_list.append(judge_one_list)

            # creating the DataFrame
            all_data = pd.DataFrame(list(zip(player_index, player_name, music_name))) 

            all_data.columns =['抽签序号', '选手姓名', '曲目名称'] #设置列名

            judge_namelist_temp=self.judge_namelist.toPlainText().split('\n')
            for qtable_column in range(int(self.judge_number.text())):
                judge_one_list=[' ']*int(self.player_number.text()) #将列数据全部填充为空格
                judge_column_name=judge_namelist_temp[qtable_column]+":"+str(qtable_column+1)+"号评委"
                all_data[judge_column_name]=judge_one_list

            #增加最高分,最低分列 ,增加实际得分列
            if self.remove_yes_radiobox.isChecked()==True:
                max_one_list=[' ']*int(self.player_number.text()) #将列数据全部填充为空格
                all_data['最高分']=max_one_list

                min_one_list=[' ']*int(self.player_number.text()) #将列数据全部填充为空格
                all_data['最低分']=min_one_list

                visual_one_list=[-1]*int(self.player_number.text()) #将列数据全部填充为空格
                all_data['实得分']=visual_one_list

                index_one_list=[' ']*int(self.player_number.text()) #将列数据全部填充为空格
                all_data['最终排名']=index_one_list

                remove_score_str=self.remove_score.text()
                self.remove_score_number=int(self.remove_score.text())
                remove_one_list=[remove_score_str]*int(self.player_number.text()) #将列数据全部填充为空格
                all_data['去掉几个最高分']=remove_one_list
            else:
                visual_one_list=[-1]*int(self.player_number.text()) #将列数据全部填充为空格
                all_data['实得分']=visual_one_list

                index_one_list=[' ']*int(self.player_number.text()) #将列数据全部填充为空格
                all_data['最终排名']=index_one_list

                remove_one_list=['0']*int(self.player_number.text()) #将列数据全部填充为空格
                all_data['去掉几个最高分']=remove_one_list

            all_data['抽签序号']=all_data['抽签序号'].astype(int)
            all_data=all_data.sort_values(by=['抽签序号']) #按照抽签序号排序
            all_data=all_data.reset_index(drop=True) #重新排列一下索引

            all_data.to_excel('temp_data/'+str(self.race_name.text())+'_临时存储数据.xlsx', sheet_name='部分信息', index=True) # index false为不写入索引
            
            #恢复所有数据初始状态
            self.all_data_df=[]
            self.have_score_number=0 #表示当前已经计分的选手数量
            self.remove_score_number=0

            #默认选择不去掉,并且设置不显示去掉几个最高分和最低分的框
            self.remove_no_radiobox.setChecked(True)
            self.remove_score.setVisible(False) #初始设置隐藏
            self.remove_score_label_now.setVisible(False) #初始设置隐藏

            #然后要清空所有的输入
            self.race_name.setText("") #比赛名称

            self.judge_number.setText("") #评委数量
            self.judge_namelist.setText("") #评委信息

            self.player_number.setText("") #选手数量
            self.player_namelist.setText("") #选手信息

            self.music_namelist.setText("") #音乐信息

            #然后清空qtablewight
            self.score_table.setRowCount(0)
            self.score_table.setColumnCount(0)
            self.score_table.clearContents()

            #设置按钮不可用
            self.race_start.setEnabled(False)
            self.save_info.setEnabled(False)

            self.setQMessageBoxoneButtonTextENToCN("提示","保存信息成功",QMessageBox.Information)
            return
        else:
            self.setQMessageBoxoneButtonTextENToCN("提示","用户取消操作",QMessageBox.Information)
            return
        return

        return


    def func_quit_code(self):
        reply_one=self.setQMessageBoxtwoButtonTextENToCN('提示', '是否退出程序?',QMessageBox.Question)
        if reply_one == QMessageBox.Yes: 
            self.setQMessageBoxoneButtonTextENToCN("提示","退出程序成功",QMessageBox.Information)
            sys.exit(0)
            return
        else:
            self.setQMessageBoxoneButtonTextENToCN("提示","用户取消操作",QMessageBox.Information)
            return
        return

    def func_text_change(self):
        #在输入框内按下enter,就会进入这里
        #获取数据,保存这个选手的得分到临时表格中
        all_data_temp=self.all_data_df

        judge_number=0
        if int(all_data_temp.loc[0,'去掉几个最高分'])>0:
            judge_number=int(all_data_temp.shape[1])-8
        else:
            judge_number=int(all_data_temp.shape[1])-6

        #遍历所有输入框,然后找到第一个为空的输入框,设置它为setFocus即可
        for com_i in range(1,judge_number+1):
            judgeone_score_input_name="judgeone_score_input_"+str(com_i)
            judgeone_score_temp = self.race_going.findChild(QLineEdit,judgeone_score_input_name)
            if judgeone_score_temp.text()=='':
                judgeone_score_temp.setFocus()
                return
            else:
                continue

        #获取数据,保存这个选手的得分到临时表格中
        all_data_temp=self.all_data_df
        print(self.all_data_df)
        reply_zero=self.setQMessageBoxtwoButtonTextENToCN('提示', '评委得分是否输入正确?',QMessageBox.Question)
        if reply_zero == QMessageBox.Yes:
            #根据评委数量来获取输入框数据
            score_list_temp=[]
            save_score_list=[]
            judge_number=0
            if int(all_data_temp.loc[0,'去掉几个最高分'])>0:
                judge_number=int(all_data_temp.shape[1])-8
            else:
                judge_number=int(all_data_temp.shape[1])-6

            print(judge_number)
            for com_i in range(1,judge_number+1):
                judgeone_score_input_name="judgeone_score_input_"+str(com_i)
                judgeone_score_temp = self.race_going.findChild(QLineEdit,judgeone_score_input_name)

                judgeone_score_temp.setReadOnly(True)  #设置为只读
                
                if judgeone_score_temp.text()=='':
                    self.setQMessageBoxoneButtonTextENToCN("提示",str(com_i)+"评委分数未填写",QMessageBox.Information)
                    return
                else:
                    score_list_temp.append(float(judgeone_score_temp.text()))
                    save_score_list.append(float(judgeone_score_temp.text()))
            
            print(score_list_temp)
            score_list_temp.sort() #对打分进行排序
            
            #设置最高分
            score_list_temp_max=max(score_list_temp)
            self.judge_com_max.setText("最高分: "+str(score_list_temp_max))
            # self.score_table.setText(score_list_temp_max)

            #设置最低分
            score_list_temp_min=min(score_list_temp)
            self.judge_com_min.setText("最低分: "+str(score_list_temp_min))

            for flag in range(self.remove_score_number):
                score_list_temp.pop() #删除最后一项
                score_list_temp.pop(0) #删除第一项

            #设置最终分数
            average_score=round(sum(score_list_temp)/len(score_list_temp),3)
            #设置
            self.judge_com_average.setText(str(average_score))

            #确定当前计算的是哪个选手,至少有一个评委
            current_player_row=0
            player_row=int(all_data_temp.shape[0])
            for player_row_index in range(0,player_row):
                print(str(all_data_temp.iloc[player_row_index,int(all_data_temp.shape[1])-3]))
                print(str(all_data_temp.iloc[player_row_index,int(all_data_temp.shape[1])-3])=='-1')
                if str(all_data_temp.iloc[player_row_index,int(all_data_temp.shape[1])-3])=='-1' or str(all_data_temp.iloc[player_row_index,int(all_data_temp.shape[1])-3])=='-1.0':
                    current_player_row=player_row_index
                    break
                current_player_row=player_row_index
            
            self.have_score_number=current_player_row
            #保存方法在有最高分和没有最高分时有所不同,有
            if int(all_data_temp.loc[0,'去掉几个最高分'])>0:
            #将这里面的数据进行覆盖  操作的是save_score_list,score_list_temp_max,score_list_temp_min
                print(save_score_list)
                for judge_number_index in range(0,judge_number):
                    print(judge_number_index)
                    all_data_temp.iloc[current_player_row,judge_number_index+3]=save_score_list[judge_number_index]   
                
                all_data_temp.iloc[current_player_row,judge_number_index+4]=score_list_temp_max 
                all_data_temp.iloc[current_player_row,judge_number_index+5]=score_list_temp_min
                all_data_temp.iloc[current_player_row,judge_number_index+6]=average_score
            else:
                for judge_number_index in range(0,judge_number):
                    all_data_temp.iloc[current_player_row,judge_number_index+3]=save_score_list[judge_number_index]   
                
                all_data_temp.iloc[current_player_row,judge_number_index+4]=average_score

            #重新保存数据表,最后再赋值回去
            all_data_temp.to_excel('temp_data/'+str(self.race_name.text())+'_临时存储数据.xlsx', sheet_name='部分信息', index=True) # index false为不写入索引

            self.all_data_df=all_data_temp
            print(self.all_data_df)

            # #清空分数框         
            # for show_i in range(1,judge_number+1):
            #     judgeone_score_input_name="judgeone_score_input_"+str(show_i)
            #     judgeone_score_temp = self.race_going.findChild(QLineEdit,judgeone_score_input_name)
            #     judgeone_score_temp.clear()
            
            self.have_score_number=self.have_score_number+1 #保存当前选手序号,只用于验证选手是否弃权
            print("self.have_score_number=",self.have_score_number)

            return
        else:
            self.setQMessageBoxoneButtonTextENToCN("提示","请检查后,继续填写",QMessageBox.Information)
            return


        return


    #单选按钮绑定
    def func_radiobox_btnstate(self,btn):
        #输出按钮1与按钮2的状态,选中还是没选中
        if btn.text()=='是':
            if btn.isChecked()==True:
                # print(btn.text()+"is selected")
                self.remove_score.setVisible(True) #初始设置隐藏
                self.remove_score_label_now.setVisible(True) #初始设置隐藏
            else:
                # print(btn.text()+"is deselected")
                return

        if btn.text()=="否":
            if btn.isChecked() == True:
                # print(btn.text() + "is selected")
                self.remove_score.setVisible(False) #初始设置隐藏
                self.remove_score_label_now.setVisible(False) #初始设置隐藏
                self.remove_score.setText("") #重新设置为空
            else:
                # print(btn.text() + "is deselected")
                return
    def setQMessageBoxtwoButtonTextENToCN(self,windowTitle, text,  qmessageIcon):
        """
        设置需要的QMessageBox的按钮提示为中文
        :param qmessageIcon: 显示图标  例如QMessageBox.Question
        :param kwargs: 确定=QMessageBox.AcceptRole
        """
        msgBox = QMessageBox()
        msgBox.setWindowTitle(windowTitle)
        msgBox.setText(text)
        msgBox.setIcon(qmessageIcon)
        msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
        buttonY = msgBox.button(QMessageBox.Yes)
        buttonY.setText('是')
        buttonN = msgBox.button(QMessageBox.No)
        buttonN.setText('否')
        ret=msgBox.exec_()
        return ret
    
    def setQMessageappENToCN(self,windowTitle, text,  qmessageIcon):
        """
        设置需要的QMessageBox的按钮提示为中文
        :param qmessageIcon: 显示图标  例如QMessageBox.Question
        :param kwargs: 确定=QMessageBox.AcceptRole
        """
        msgBox = QMessageBox()
        msgBox.setWindowTitle(windowTitle)
        msgBox.setText(text)
        msgBox.setIcon(qmessageIcon)
        msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
        buttonY = msgBox.button(QMessageBox.Yes)
        buttonY.setText('比赛暂停')
        buttonN = msgBox.button(QMessageBox.No)
        buttonN.setText('比赛结束')
        ret=msgBox.exec_()
        return ret


    def setQMessageBoxoneButtonTextENToCN(self,windowTitle, text,  qmessageIcon):
        """
        设置需要的QMessageBox的按钮提示为中文
        :param qmessageIcon: 显示图标  例如QMessageBox.Question
        :param kwargs: 确定=QMessageBox.AcceptRole
        """
        msgBox = QMessageBox()
        msgBox.setWindowTitle(windowTitle)
        msgBox.setText(text)
        msgBox.setIcon(qmessageIcon)
        msgBox.setStandardButtons(QMessageBox.Yes)
        buttonY = msgBox.button(QMessageBox.Yes)
        buttonY.setText('是')
        msgBox.exec_()

    #根据输入框来读取信息已经无误
    def func_race_info(self):
        reply_one=self.setQMessageBoxtwoButtonTextENToCN('提示', '是否确认比赛准备信息无误?',QMessageBox.Question)

        if reply_one == QMessageBox.No:    
            self.setQMessageBoxoneButtonTextENToCN("提示","请继续填写",QMessageBox.Information)
            return

        #检查是否有未填写数据
        if self.race_name.text()=='' or self.judge_number.text()=='' or self.judge_namelist.toPlainText()=='' or self.player_number.text()==''  or self.player_namelist.toPlainText()=='' or self.music_namelist.toPlainText()=='':
            self.setQMessageBoxoneButtonTextENToCN("提示","有未填写数据",QMessageBox.Information)
            return
        
        if self.remove_score.text()=='' and self.remove_yes_radiobox.isChecked()==True:
            self.setQMessageBoxoneButtonTextENToCN("提示","有未填写数据",QMessageBox.Information)
            return
        #检查评委数量是否足够
        if  int(self.judge_number.text())!=len(self.judge_namelist.toPlainText().split('\n')):
            self.setQMessageBoxoneButtonTextENToCN("提示","评委数量与评委姓名数量不对应,请重新填写",QMessageBox.Information)
            return
        
        #检查选手数量是否足够
        if  int(self.player_number.text())!=len(self.player_namelist.toPlainText().split('\n')):
            self.setQMessageBoxoneButtonTextENToCN("提示","选手数量与选手姓名数量不对应,请重新填写",QMessageBox.Information)
            return
        
        #检查曲目数量是否足够
        if  int(self.player_number.text())!=len(self.music_namelist.toPlainText().split('\n')):
            self.setQMessageBoxoneButtonTextENToCN("提示","音乐数量与选手数量不对应,请重新填写",QMessageBox.Information)
            return

        if reply_one == QMessageBox.Yes:    
            self.setQMessageBoxoneButtonTextENToCN("提示","比赛准备信息填写成功",QMessageBox.Information)          
            self.score_table.setColumnCount(3)
            self.score_table.setRowCount(int(self.player_number.text()))
            column_namelist_temp=["抽签序号","选手姓名","曲目名称"]
            self.score_table.setHorizontalHeaderLabels(column_namelist_temp) #设置行表头

            player_namelist_temp=self.player_namelist.toPlainText().split('\n')
            music_namelist_temp=self.music_namelist.toPlainText().split('\n')
            for i in range(int(self.player_number.text())):
                self.score_table.setItem(i,0, QTableWidgetItem(str(i+1)))
                self.score_table.setItem(i,1, QTableWidgetItem(player_namelist_temp[i]))
                self.score_table.setItem(i,2, QTableWidgetItem(music_namelist_temp[i]))

            #设置按钮可用
            self.race_start.setEnabled(True)
            self.save_info.setEnabled(True)

            #给设置自适应宽度
            self.score_table.resizeColumnToContents(0)
            self.score_table.resizeColumnToContents(1)
            self.score_table.resizeColumnToContents(2)
            return
        return

    #这是按照全新比赛的操作
    def func_race_start(self):
        #先将读取的数据保存起来,以tablewight数据表信息为准
        player_index=[]
        player_name=[]
        music_name=[]
        for qtable_row in range(int(self.player_number.text())):
            player_index.append(self.score_table.item(qtable_row,0).text())  #获取某行某列item中的x信息
            player_name.append(self.score_table.item(qtable_row,1).text())  #获取某行某列item中的x信息
            music_name.append(self.score_table.item(qtable_row,2).text())  #获取某行某列item中的x信息
        
        judge_all_list=[]
        for qtable_column in range(int(self.judge_number.text())):
            judge_one_list=[' ']*int(self.player_number.text()) #将列数据全部填充为空格
            judge_all_list.append(judge_one_list)

        # creating the DataFrame
        all_data = pd.DataFrame(list(zip(player_index, player_name, music_name))) 

        all_data.columns =['抽签序号', '选手姓名', '曲目名称'] #设置列名

        judge_namelist_temp=self.judge_namelist.toPlainText().split('\n')
        for qtable_column in range(int(self.judge_number.text())):
            judge_one_list=[' ']*int(self.player_number.text()) #将列数据全部填充为空格
            judge_column_name=judge_namelist_temp[qtable_column]+":"+str(qtable_column+1)+"号评委"
            all_data[judge_column_name]=judge_one_list

        #增加最高分,最低分列 ,增加实际得分列
        if self.remove_yes_radiobox.isChecked()==True:
            max_one_list=[' ']*int(self.player_number.text()) #将列数据全部填充为空格
            all_data['最高分']=max_one_list

            min_one_list=[' ']*int(self.player_number.text()) #将列数据全部填充为空格
            all_data['最低分']=min_one_list

            visual_one_list=[-1]*int(self.player_number.text()) #将列数据全部填充为空格
            all_data['实得分']=visual_one_list

            index_one_list=[' ']*int(self.player_number.text()) #将列数据全部填充为空格
            all_data['最终排名']=index_one_list

            remove_score_str=self.remove_score.text()
            self.remove_score_number=int(self.remove_score.text())
            remove_one_list=[remove_score_str]*int(self.player_number.text()) #将列数据全部填充为空格
            all_data['去掉几个最高分']=remove_one_list
        else:
            visual_one_list=[-1]*int(self.player_number.text()) #将列数据全部填充为空格
            all_data['实得分']=visual_one_list

            index_one_list=[' ']*int(self.player_number.text()) #将列数据全部填充为空格
            all_data['最终排名']=index_one_list

            remove_one_list=['0']*int(self.player_number.text()) #将列数据全部填充为空格
            all_data['去掉几个最高分']=remove_one_list

        all_data['抽签序号']=all_data['抽签序号'].astype(int)
        all_data=all_data.sort_values(by=['抽签序号']) #按照抽签序号排序
        all_data=all_data.reset_index(drop=True) #重新排列一下索引

        all_data.to_excel('temp_data/'+str(self.race_name.text())+'_临时存储数据.xlsx', sheet_name='部分信息', index=True) # index false为不写入索引
        self.all_data_df=all_data

        #界面展示与隐藏
        self.race_prepare.setVisible(False) #初始设置隐藏
        self.race_going.setVisible(True) #初始设置隐藏

        #设置比赛名称
        self.race_going_name.setText(self.race_name.text())

        # 展示当前选手信息,需要读取数据表的1号来展示
        self.current_player.setText("当前选手:1号选手"+str(all_data.loc[0,'选手姓名']))

        #根据评委数量来显示评委框
        judge_number=int(self.judge_number.text())
        judge_one_namelist=self.judge_namelist.toPlainText().split('\n')
        for show_i in range(1,judge_number+1):
            judge_one_label_name="judge_one_label_"+str(show_i)
            judge_one_temp = self.race_going.findChild(QLabel,judge_one_label_name)
            #设置评委label的text
            judge_one_temp.setText(judge_one_namelist[show_i-1]+":"+str(show_i)+"号评委")
            judge_one_temp.setVisible(True)

            judgeone_score_input_name="judgeone_score_input_"+str(show_i)
            judgeone_score_temp = self.race_going.findChild(QLineEdit,judgeone_score_input_name)
            judgeone_score_temp.setVisible(True)

        #根据是否需要去掉最高分和最低分来展示
        if self.remove_yes_radiobox.isChecked()==True:
            #去掉了最高分和最低分,那么就显示所有的
            for show_i in range(1,5):
                judge_com_label_name="judge_com_label_"+str(show_i)
                judge_com_temp = self.race_going.findChild(QLabel,judge_com_label_name)
                judge_com_temp.setVisible(True)
        else:
            #不去掉,则只显示,实际得分
            self.judge_com_average_label.setVisible(True)
            self.judge_com_average.setVisible(True)
        
        return

    #下一个选手
    def func_next_player(self):
        #获取数据,保存这个选手的得分到临时表格中
        all_data_temp=self.all_data_df
        print(self.all_data_df)
        
        #先询问是否计算最终得分,获取输入框信息,计算得分,然后展示出来,展示出来之后将信息保存到临时数据表中
        reply_one=self.setQMessageBoxtwoButtonTextENToCN('提示', '是否跳到下一个选手?',QMessageBox.Question)
        if reply_one == QMessageBox.Yes:    
            #先判断选手是否是弃权
            #确定当前计算的是哪个选手,至少有一个评委
            current_player_row=0
            #找到第一个未计分的选手
            player_row=int(all_data_temp.shape[0])
            print("player_row=",player_row)
            for player_row_index in range(0,player_row):
                print(str(all_data_temp.iloc[player_row_index,int(all_data_temp.shape[1])-3]))
                print(str(all_data_temp.iloc[player_row_index,int(all_data_temp.shape[1])-3])=='-1')
                if str(all_data_temp.iloc[player_row_index,int(all_data_temp.shape[1])-3])=='-1' or str(all_data_temp.iloc[player_row_index,int(all_data_temp.shape[1])-3])=='-1.0':
                    current_player_row=player_row_index
                    break
                current_player_row=player_row_index

            self.have_score_number=current_player_row+1
            print("current_player_row=",current_player_row)
            print("player_row=",player_row)
            print("self.have_score_number=",self.have_score_number)

            if self.have_score_number==player_row:
                #这时候比赛就提示已经统计完所有选手分数
                self.setQMessageBoxoneButtonTextENToCN("提示","已统计完所有选手分数",QMessageBox.Information)  
                self.current_player.setText("本场比赛所有选手已比赛完毕,请退出程序")
                #保存方法在有最高分和没有最高分时有所不同,有
                judge_number=0
                if int(all_data_temp.loc[0,'去掉几个最高分'])>0:
                    judge_number=int(all_data_temp.shape[1])-8
                else:
                    judge_number=int(all_data_temp.shape[1])-6
                #清空分数框
                for show_i in range(1,judge_number+1):
                    judgeone_score_input_name="judgeone_score_input_"+str(show_i)
                    judgeone_score_temp = self.race_going.findChild(QLineEdit,judgeone_score_input_name)
                    judgeone_score_temp.clear()

                    # judgeone_score_temp.setReadOnly(False)  #恢复成可修改

                if int(all_data_temp.loc[0,'去掉几个最高分'])>0:
                    #设置最高分
                    self.judge_com_max.setText("最高分: ")
                    #设置最低分
                    self.judge_com_min.setText("最低分: ")
                    #设置最终分数
                    self.judge_com_average.setText(str(''))
                else:
                    #设置最终分数
                    self.judge_com_average.setText(str(''))

                self.judgeone_score_1.setFocus() #设置光标

                #设置按钮不可点击
                self.next_player.setEnabled(False)
                #选手弃权按钮
                self.player_quit.setEnabled(False)


                return
            else:
                judge_number=0
                if int(all_data_temp.loc[0,'去掉几个最高分'])>0:
                    judge_number=int(all_data_temp.shape[1])-8
                else:
                    judge_number=int(all_data_temp.shape[1])-6
                #current_player_row表示当前第一个空的数据选手序号,self.have_score_number表示已经填写的选手数据序号,
                # 如果不一样,说明这个选手弃权了,需要把所有数据填写完毕
                # 如果计算数据表里的和当前的一样,那就说明没有弃权
                print("self.have_score_number=",self.have_score_number)
                print("current_player_row=",current_player_row)
                #正常切换,只需要重新设置一些信息即可
                #清空分数框
                for show_i in range(1,judge_number+1):
                    judgeone_score_input_name="judgeone_score_input_"+str(show_i)
                    judgeone_score_temp = self.race_going.findChild(QLineEdit,judgeone_score_input_name)
                    judgeone_score_temp.clear()

                    judgeone_score_temp.setReadOnly(False)  #恢复成可修改
                
                if int(all_data_temp.loc[0,'去掉几个最高分'])>0:
                    #重新设置文字信息
                    #设置下一个选手
                    self.current_player.setText("当前选手:"+str(current_player_row+1)+"号选手")
                    #设置最高分
                    self.judge_com_max.setText("最高分: ")
                    #设置最低分
                    self.judge_com_min.setText("最低分: ")
                    #设置最终分数
                    self.judge_com_average.setText(str(''))
                else:
                    #设置下一个选手
                    self.current_player.setText("当前选手:"+str(current_player_row+1)+"号选手")
                    #设置最终分数
                    self.judge_com_average.setText(str(''))
                
                self.judgeone_score_1.setFocus() #设置光标
                return
              
            return
        else:
            self.setQMessageBoxoneButtonTextENToCN("提示","用户取消操作",QMessageBox.Information)
            return

        return

    def func_player_quit(self):
        #获取数据,保存这个选手的得分到临时表格中
        all_data_temp=self.all_data_df
        print(self.all_data_df)
        current_player_row=0
        #先询问是否计算最终得分,获取输入框信息,计算得分,然后展示出来,展示出来之后将信息保存到临时数据表中
        reply_one=self.setQMessageBoxtwoButtonTextENToCN('提示', '当前选手是否弃权?',QMessageBox.Question)
        if reply_one == QMessageBox.Yes:    
            #选手弃权
            judge_number=0
            if int(all_data_temp.loc[0,'去掉几个最高分'])>0:
                judge_number=int(all_data_temp.shape[1])-8
            else:
                judge_number=int(all_data_temp.shape[1])-6
            #清空分数框
            for show_i in range(1,judge_number+1):
                judgeone_score_input_name="judgeone_score_input_"+str(show_i)
                judgeone_score_temp = self.race_going.findChild(QLineEdit,judgeone_score_input_name)
                judgeone_score_temp.clear()

            #找到第一个未计分的选手
            player_row=int(all_data_temp.shape[0])
            print("player_row=",player_row)
            for player_row_index in range(0,player_row):
                print(str(all_data_temp.iloc[player_row_index,int(all_data_temp.shape[1])-3]))
                print(str(all_data_temp.iloc[player_row_index,int(all_data_temp.shape[1])-3])=='-1')
                if str(all_data_temp.iloc[player_row_index,int(all_data_temp.shape[1])-3])=='-1' or str(all_data_temp.iloc[player_row_index,int(all_data_temp.shape[1])-3])=='-1.0':
                    current_player_row=player_row_index
                    break
                current_player_row=player_row_index

            self.have_score_number=current_player_row
            if self.have_score_number==player_row:
                #这时候比赛就提示已经统计完所有选手分数
                self.setQMessageBoxoneButtonTextENToCN("提示","已统计完所有选手分数",QMessageBox.Information)

                #设置按钮不可点击
                self.next_player.setEnabled(False)
                #选手弃权按钮
                self.player_quit.setEnabled(False)

                return
            else:
                #保存方法在有最高分和没有最高分时有所不同,有
                if int(all_data_temp.loc[0,'去掉几个最高分'])>0:
                #将这里面的数据进行覆盖  操作的是save_score_list,score_list_temp_max,score_list_temp_min
                    for judge_number_index in range(0,judge_number):
                        print(judge_number_index)
                        all_data_temp.iloc[current_player_row,judge_number_index+3]=0  
                    
                    all_data_temp.iloc[current_player_row,judge_number_index+4]=0 
                    all_data_temp.iloc[current_player_row,judge_number_index+5]=0
                    all_data_temp.iloc[current_player_row,judge_number_index+6]=0
                else:
                    for judge_number_index in range(0,judge_number):
                        all_data_temp.iloc[current_player_row,judge_number_index+3]=0   
                    
                    all_data_temp.iloc[current_player_row,judge_number_index+4]=0

                print("弃权后all_data_temp=",all_data_temp)
                #重新保存数据表,最后再赋值回去
                all_data_temp.to_excel('temp_data/'+str(self.race_name.text())+'_临时存储数据.xlsx', sheet_name='部分信息', index=True) # index false为不写入索引
                self.all_data_df=all_data_temp

                self.have_score_number=self.have_score_number+1
                self.setQMessageBoxoneButtonTextENToCN("提示",str(current_player_row+1)+"号选手"+str(all_data_temp.loc[current_player_row,'选手姓名'])+"弃权成功,马上跳到下一个选手",QMessageBox.Information)


                if self.have_score_number==player_row:
                    #这时候比赛就提示已经统计完所有选手分数
                    self.setQMessageBoxoneButtonTextENToCN("提示","已统计完所有选手分数",QMessageBox.Information)
                    self.current_player.setText("本场比赛所有选手已比赛完毕,请退出程序")
                    #保存方法在有最高分和没有最高分时有所不同,有
                    #清空分数框
                    for show_i in range(1,judge_number+1):
                        judgeone_score_input_name="judgeone_score_input_"+str(show_i)
                        judgeone_score_temp = self.race_going.findChild(QLineEdit,judgeone_score_input_name)
                        judgeone_score_temp.clear()

                    if int(all_data_temp.loc[0,'去掉几个最高分'])>0:
                        #设置最高分
                        self.judge_com_max.setText("最高分: ")
                        #设置最低分
                        self.judge_com_min.setText("最低分: ")
                        #设置最终分数
                        self.judge_com_average.setText(str(''))
                    else:
                        #设置最终分数
                        self.judge_com_average.setText(str(''))

                    self.judgeone_score_1.setFocus() #设置光标

                    #设置按钮不可点击
                    self.next_player.setEnabled(False)
                    #选手弃权按钮
                    self.player_quit.setEnabled(False)

                    return
                else:
                    #跳转到下一个选手
                    if int(all_data_temp.loc[0,'去掉几个最高分'])>0:
                        self.current_player.setText("当前选手:"+str(current_player_row+2)+"号选手"+str(all_data_temp.loc[current_player_row+1,'选手姓名']))
                        #设置最高分
                        self.judge_com_max.setText("最高分: ")
                        #设置最低分
                        self.judge_com_min.setText("最低分: ")
                        #设置最终分数
                        self.judge_com_average.setText(str(''))
                    else:
                        #设置下一个选手
                        self.current_player.setText("当前选手:"+str(current_player_row+2)+"号选手"+str(all_data_temp.loc[current_player_row+1,'选手姓名']))
                        #设置最终分数
                        self.judge_com_average.setText(str(''))

                    self.judgeone_score_1.setFocus() #设置光标
                    


                return
        else:
            self.setQMessageBoxoneButtonTextENToCN("提示","用户取消操作",QMessageBox.Information)
            return

    #结束比赛
    def func_exit_race(self):
        #获取数据,保存这个选手的得分到临时表格中
        all_data_temp=self.all_data_df
        print(self.all_data_df)
        
        reply_zero=self.setQMessageappENToCN('提示', '请选择退出程序的原因?',QMessageBox.Question)
        if reply_zero == QMessageBox.Yes:
            reply_one=self.setQMessageBoxtwoButtonTextENToCN('提示', '是否确认选择比赛暂停?',QMessageBox.Question)
            if reply_one == QMessageBox.Yes: 
                self.setQMessageBoxoneButtonTextENToCN("提示","比赛暂停,临时数据保存成功",QMessageBox.Information)
                sys.exit(0)
                return
            else:
                self.setQMessageBoxoneButtonTextENToCN("提示","用户取消操作",QMessageBox.Information)
                return
            return
        else:
            reply_two=self.setQMessageBoxtwoButtonTextENToCN('提示', '是否确认选择比赛结束?',QMessageBox.Question)
            if reply_two == QMessageBox.Yes: 
                #导出数据,清空临时表
                all_data_temp=all_data_temp.sort_values(by=['实得分'],ascending=False) #按照抽签序号排序
                all_data_temp=all_data_temp.reset_index(drop=True) #重新排列一下索引
                
                player_row=int(all_data_temp.shape[0])
                print("player_row=",player_row)
                for player_row_index in range(0,player_row):
                    all_data_temp.iloc[player_row_index,int(all_data_temp.shape[1])-2]=player_row_index+1

                all_data_temp.to_excel('save_data/'+str(self.race_name.text())+'_最终数据全部备份版.xlsx', sheet_name='部分信息', index=True) # index false为不写入索引
                #保留特定数据
                all_data_temp_new = all_data_temp[['抽签序号', '选手姓名', '曲目名称','实得分','最终排名']]
                
                # 创建一个新的文档
                doc_part=docx.Document()
                # 添加标题
                para_heading=doc_part.add_heading(str(self.race_name.text()), 5)
                para_heading.alignment=docx.enum.text.WD_ALIGN_PARAGRAPH.CENTER#设置为左对齐


                # 添加表格
                table_part = doc_part.add_table(rows=all_data_temp_new.shape[0]+1, cols=all_data_temp_new.shape[1])

                # 添加表头
                hdr_cells = table_part.rows[0].cells
                for i in range(all_data_temp_new.shape[1]):
                    hdr_cells[i].text = all_data_temp_new.columns[i]

                # 添加每一行数据
                for i in range(all_data_temp_new.shape[0]):
                    row_cells = table_part.rows[i+1].cells
                    for j in range(all_data_temp_new.shape[1]):
                        row_cells[j].text = str(all_data_temp_new.values[i,j])

                # 保存文件
                doc_part.save('save_data/'+str(self.race_name.text())+'_最终数据简洁版.docx')
                # del_files("temp_data/")
                self.setQMessageBoxoneButtonTextENToCN("提示","最终数据导出成功,本次比赛临时数据已清除",QMessageBox.Information)
                sys.exit(0)
                return
            else:
                self.setQMessageBoxoneButtonTextENToCN("提示","用户取消操作",QMessageBox.Information)
                return
            return

        return

    #根据文件来读取信息,这个是不一样的,最后写才对,这个继续前次比赛,比赛的信息设置与全新比赛不相同
    def func_continue_race(self):
        #先是询问是否继续上次比赛
        reply_one=self.setQMessageBoxtwoButtonTextENToCN('提示', '是否继续上次比赛?',QMessageBox.Question)
        if reply_one == QMessageBox.Yes:        
            folderName, _ = QFileDialog.getOpenFileName(self, "选择xlsx文件", "", "*.xlsx")        
            race_name=folderName.split("/")[-1].split("_")[0]
            self.race_name.setText(race_name)
            self.all_data_df=pd.read_excel('temp_data/'+folderName.split("/")[-1],index_col=0)
            
            all_data_temp=self.all_data_df

            #界面展示与隐藏
            self.race_prepare.setVisible(False) #初始设置隐藏
            self.race_going.setVisible(True) #初始设置隐藏

            #设置比赛名称
            self.race_going_name.setText(race_name)

            #设置选手初值
            current_player_row=0
            #找到第一个未计分的选手
            player_row=int(all_data_temp.shape[0])
            print("player_row=",player_row)
            for player_row_index in range(0,player_row):
                print(str(all_data_temp.iloc[player_row_index,int(all_data_temp.shape[1])-3]))
                print(str(all_data_temp.iloc[player_row_index,int(all_data_temp.shape[1])-3])=='-1')
                if str(all_data_temp.iloc[player_row_index,int(all_data_temp.shape[1])-3])=='-1' or str(all_data_temp.iloc[player_row_index,int(all_data_temp.shape[1])-3])=='-1.0':
                    current_player_row=player_row_index
                    break
                current_player_row=player_row_index

            self.have_score_number=current_player_row
            judge_number=0

            self.remove_score_number=int(all_data_temp.loc[0,'去掉几个最高分'])
            
            #设置界面信息:当前选手,是否展示实得分最高分,评委名字
            if int(all_data_temp.loc[0,'去掉几个最高分'])>0:
                self.current_player.setText("当前选手:"+str(current_player_row+1)+"号选手")
                for show_i in range(1,5):
                    judge_com_label_name="judge_com_label_"+str(show_i)
                    judge_com_temp = self.race_going.findChild(QLabel,judge_com_label_name)
                    judge_com_temp.setVisible(True)
                #设置最高分
                self.judge_com_max.setText("最高分: ")
                #设置最低分
                self.judge_com_min.setText("最低分: ")
                #设置最终分数
                self.judge_com_average.setText(str(''))
                judge_number=int(all_data_temp.shape[1])-8
            else:
                #设置下一个选手
                self.current_player.setText("当前选手:"+str(current_player_row+1)+"号选手")
                self.judge_com_average_label.setVisible(True)
                self.judge_com_average.setVisible(True)
                #设置最终分数
                self.judge_com_average.setText(str(''))
                judge_number=int(all_data_temp.shape[1])-6

            judge_namelist=[column for column in all_data_temp]
            judge_one_namelist=judge_namelist[3:3+judge_number]
            for show_i in range(1,judge_number+1):
                judge_one_label_name="judge_one_label_"+str(show_i)
                judge_one_temp = self.race_going.findChild(QLabel,judge_one_label_name)
                #设置评委label的text
                judge_one_temp.setText(judge_one_namelist[show_i-1].split(":")[0]+":"+str(show_i)+"号评委")
                judge_one_temp.setVisible(True)

                judgeone_score_input_name="judgeone_score_input_"+str(show_i)
                judgeone_score_temp = self.race_going.findChild(QLineEdit,judgeone_score_input_name)
                judgeone_score_temp.setVisible(True)

        else:
            self.setQMessageBoxoneButtonTextENToCN("提示","用户取消操作",QMessageBox.Information)
        return


if __name__ == '__main__':
    app=QApplication(sys.argv)  
    w = MyWindow()
    w.ui.show()
    app.exec()

程序打包成exe

指令 安装pyinstall库

文件目录
根路径
	getscore.py
	assets
		xxx.ui
	save_data
	temp_data
	

pyinstaller -F -w getscore.py --add-data ".\\assets\\*;.\\assets"

参考文章

感谢以下作者的知识分享

PyQt入门教程 - 随笔分类 - 锅边糊 - 博客园 (cnblogs.com)

【创作不易,望点赞收藏,若有疑问,请留言,谢谢】

本文介绍基于
R
语言中的
raster
包,遍历文件夹,读取
文件夹
下的
大量栅格遥感影像
,并逐一对
每一景栅格图像
加以
拼接

融合
,使得
全部栅格遥感影像
拼接为
完整的一景图像
的方法。

其中,本文是用
R
语言来进行操作的;如果希望基于
Python
语言实现类似的批量拼接、镶嵌操作,大家可以参考
Python arcpy创建栅格、批量拼接栅格

Python ArcPy批量拼接长时间序列栅格图像
这两篇文章。

首先,来看一下本文所需实现的需求。如下图所示,现有一个文件夹,其中含有大量
栅格遥感影像
;这些遥感影像均为
同一成像时间

不同空间范围
的遥感影像。我们希望做到的,就是对这些遥感影像加以
拼接
,最终的结果图像就是一景
将这里各个图像拼接后的
大图像。

image

明确了需求,我们即可开始代码的撰写。本文所用到的代码如下所示。

library(raster)
tif_file_name <- list.files(path = r"(E:\02_Project\01_Chlorophyll\Select\Result)", pattern = ".tif$", full.names = TRUE, ignore.case = TRUE)
tif_file_list <- list()
for (i in 1:length(tif_file_name)){
  tif_file_list[i] <- raster(tif_file_name[i])
}

tif_file_list$fun <- max
tif_file_list$na.rm <- TRUE
tif_mosaic <- do.call(mosaic, tif_file_list)
plot(tif_mosaic)

# tif_merge <- do.call(merge, tif_file_list)

rf <- writeRaster(tif_mosaic, filename = r"(E:\02_Project\01_Chlorophyll\Select\NewClip\LCC_SC_3.tif)", overwrite = TRUE)

首先,需要通过
library(raster)
代码,导入本文所需的
R
语言
raster
包;关于这一包的配置,大家可以参考
基于R语言的raster包读取遥感影像
。接下来,我们通过
list.files()
函数,遍历指定文件夹,从而获取当前文件夹下所包含的全部
.tif
格式的遥感影像,也就是全部待拼接的遥感影像。

接下来,我们需要为栅格遥感影像的拼接做准备——也就是
for
循环内部的内容。此时,
tif_file_name
变量中存放的是指定文件夹下的全部栅格遥感影像的
文件名称
,而不是
遥感影像文件自身
;而接下来我们进行拼接、融合的函数,都需要保证函数参数中的遥感影像是一个
栅格对象

Raster* object
)类型的变量。因此,我们需要在这个
for
循环中,通过
raster()
函数,将每一个遥感影像的文件名(
字符串
类型)转为
栅格对象
类型。至于什么是
栅格对象
类型的变量,我们可以参考下图:其中
Formal class RasterLayer
即表示这一变量为
栅格对象
类型的。

接下来,代码分为
2
个部分。其中,
for
循环后的
4
行代码是第一部分,为
栅格拼接
的代码;同时为了对比栅格拼接与栅格融合的操作,这里还将
栅格融合
的代码也一并列出了,也就是注释掉的那一行代码。

我们首先来看第一部分代码,这里通过
mosaic()
函数来实现栅格遥感影像的拼接。这一函数原本的参数中,只有
2
个栅格对象(
Raster* object
)类型的参数,换句话说就是原本这个函数只能同时拼接
2
个栅格遥感影像;如果我们有更多的遥感影像,就需要每一次拼接
2
个栅格图像,不断重复这一操作,直到全部的栅格遥感影像拼接完毕。这样操作无疑是比较麻烦的,因此我们需要借助
do.call()
函数来实现
2
个以上栅格的拼接工作——这个
do.call()
函数可以接受
可变数量
的参数,例如本文中我们需要对大量栅格遥感影像加以逐一拼接,具体有多少景遥感影像我们自己也不一定确定,且也不关心;因此就结合这一函数,将刚刚已经转为栅格对象(
Raster* object
)类型的图像所组成的列表
tif_file_list
作为参数,用
do.call()
函数来调用
mosaic()
函数,直到将
tif_file_list
列表中全部的栅格对象(
Raster* object
)类型的元素都带入到
mosaic()
函数运行后,
do.call()
函数就结束了。

此外,由于
mosaic()
函数在运行时,除了两个栅格对象(
Raster* object
)类型的参数,还有其他的一些辅助参数,比如拼接时重叠区域该如何处理、处理时是否考虑
NoData
值的影响等;由于我们时通过
do.call()
函数来调用
mosaic()
函数,因此这些参数就不太好直接指定了。因此,我们可以通过
$
运算符,将
mosaic()
函数所需要的其他参数一并放入
tif_file_list
中,在后期
do.call()
函数调用
mosaic()
函数时,将同时读取这些参数,起到将参数传递到
mosaic()
函数中的功能。其中,在本文中我们需要指定
mosaic()
函数的
fun
参数与
na.rm
参数,二者分别是指拼接时重叠区域像元值的计算方法,以及计算重叠区域像元值时,是否考虑
NoData
值的影响;我们将这
2
个参数分别设定为
max

TRUE
,二者分别是指重叠区域的像元以
2
景遥感影像中的
最大值像元
为准,以及在计算时不考虑
NoData
值的影响。

接下来,就是第二部分,即栅格融合的代码;在这里,我们通过
merge()
函数来实现遥感影像的融合。其实,这里的
merge()
函数与前述的
mosaic()
函数功能大致一样,但
merge()
函数在处理重叠区域时,默认选择位于
顶层
的遥感影像的像元数值,就没有
mosaic()
函数中的这么多计算方法选择了。

最后,这里末尾的一句代码,就是将结果图像通过
writeRaster()
函数加以保存;这句代码的解释大家同样参考
R语言求取大量遥感影像的平均值、标准差:raster库
这篇文章即可。

随后,运行上述代码,我们就可以获得将指定文件夹内全部栅格遥感影像加以
拼接
(执行代码中的
第一部分
)或者
融合
(执行代码中的
第二部分
)的结果了。

至此,大功告成。

1.使用
ExecutorService

CountDownLatch
的方法示例

在Java中,当我们使用线程池(如
ExecutorService
)来执行异步任务时,常常需要知道所有任务是否都已经完成。
ExecutorService
接口提供了几种方式来处理这种情况,但最常用的是
shutdown()

awaitTermination()
方法的组合,或者使用
Future

CompletionService
。这里我将提供一个使用
ExecutorService

CountDownLatch
的示例,因为
CountDownLatch
提供了一种直观的方式来等待一组线程完成。

首先,我们定义几个任务,然后使用
ExecutorService
来异步执行它们,并使用
CountDownLatch
来等待所有任务完成。

import java.util.concurrent.*;  
  
public class ThreadPoolExample {  
  
    public static void main(String[] args) throws InterruptedException {  
        // 创建一个包含固定数量线程的线程池  
        ExecutorService executorService = Executors.newFixedThreadPool(4);  
  
        // 定义任务数量  
        int taskCount = 10;  
  
        // 使用CountDownLatch来等待所有任务完成  
        final CountDownLatch latch = new CountDownLatch(taskCount);  
  
        // 提交任务到线程池  
        for (int i = 0; i < taskCount; i++) {  
            int taskId = i;  
            executorService.submit(() -> {  
                // 模拟任务执行  
                try {  
                    Thread.sleep(1000); // 假设每个任务需要1秒  
                } catch (InterruptedException e) {  
                    Thread.currentThread().interrupt();  
                }  
                System.out.println("任务 " + taskId + " 完成");  
                // 每完成一个任务,计数减一  
                latch.countDown();  
            });  
        }  
  
        // 等待所有任务完成  
        System.out.println("等待所有任务完成...");  
        latch.await(); // 阻塞当前线程,直到latch的计数达到零  
        System.out.println("所有任务完成!");  
  
        // 关闭线程池  
        executorService.shutdown();  
  
        // 可选:等待线程池中的线程都执行完毕  
        try {  
            if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {  
                // 线程池没有在规定时间内关闭,则强制关闭  
                executorService.shutdownNow();  
            }  
        } catch (InterruptedException e) {  
            // 当前线程在等待过程中被中断  
            executorService.shutdownNow();  
            Thread.currentThread().interrupt();  
        }  
    }  
}

在这个例子中,我们首先创建了一个固定大小的线程池(这里使用4个线程)。然后,我们定义了一个
CountDownLatch
,其计数被初始化为任务的数量(这里为10)。对于每个任务,我们都向线程池提交了一个
Runnable
,其中包含了任务的执行逻辑和
latch.countDown()
调用,以确保每次任务完成时都会减少
CountDownLatch
的计数。

主线程通过调用
latch.await()
来等待,直到所有任务都调用了
countDown()
(即计数达到零),然后才能继续执行。这确保了主线程会等待所有任务完成后再继续。

最后,我们关闭了线程池,并通过调用
awaitTermination()
来可选地等待线程池中的所有线程都执行完毕。如果线程池没有在指定时间内关闭,则调用
shutdownNow()
来尝试立即停止所有正在执行的任务。

这个示例提供了处理异步任务并等待它们完成的一种有效方式,适用于需要等待所有任务完成再继续的场景。

2.使用
ExecutorService

invokeAll
方法和
Future
列表的方法示例

除了使用
CountDownLatch
之外,还有其他方法可以判断线程池中的所有任务是否执行完成。以下是一个使用
ExecutorService

invokeAll
方法和
Future
列表的示例,这种方法适用于我们有一组已知的任务(
Callable
)需要并行执行,并且我们需要等待所有任务完成并获取它们的结果。

import java.util.ArrayList;  
import java.util.List;  
import java.util.concurrent.*;  
  
public class ThreadPoolFutureExample {  
  
    public static void main(String[] args) throws InterruptedException, ExecutionException {  
        // 创建一个包含固定数量线程的线程池  
        ExecutorService executorService = Executors.newFixedThreadPool(4);  
  
        // 创建一个Callable任务列表  
        List<Callable<String>> tasks = new ArrayList<>();  
        for (int i = 0; i < 10; i++) {  
            final int taskId = i;  
            tasks.add(() -> {  
                // 模拟任务执行  
                Thread.sleep(1000); // 假设每个任务需要1秒  
                return "任务 " + taskId + " 完成";  
            });  
        }  
  
        // 使用invokeAll提交所有任务,这将返回一个Future列表  
        List<Future<String>> futures = executorService.invokeAll(tasks);  
  
        // 遍历Future列表,获取每个任务的结果  
        for (Future<String> future : futures) {  
            // get()会阻塞,直到对应的任务完成  
            System.out.println(future.get());  
        }  
  
        // 关闭线程池  
        executorService.shutdown();  
  
        // 可选:等待线程池中的线程都执行完毕  
        try {  
            if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {  
                // 线程池没有在规定时间内关闭,则强制关闭  
                executorService.shutdownNow();  
            }  
        } catch (InterruptedException e) {  
            // 当前线程在等待过程中被中断  
            executorService.shutdownNow();  
            Thread.currentThread().interrupt();  
        }  
    }  
}  
  
// 注意:这里使用了Lambda表达式和方法引用来简化Callable的创建  
// 实际使用中,你可能需要实现Callable接口或使用匿名内部类

在这个例子中,我们创建了一个
ExecutorService
和一个
Callable
任务列表。每个
Callable
任务都会返回一个字符串,表示任务完成的信息。我们使用
invokeAll
方法提交了所有任务,并立即获得了一个
Future
列表,每个
Future
都代表了一个任务的执行结果。

然后,我们遍历这个
Future
列表,并对每个
Future
调用
get()
方法。
get()
方法会阻塞当前线程,直到对应的任务完成并返回结果。这样,我们就能确保在继续执行之前,所有任务都已经完成。

最后,我们关闭了线程池,并等待所有线程都执行完毕(或超时后强制关闭)。

请注意,虽然这个示例使用了
Callable

Future
,但它并没有直接提供一个“是否所有任务都已完成”的布尔值。然而,通过遍历
Future
列表并调用
get()
,我们实际上已经达到了等待所有任务完成的效果。如果我们只需要知道是否所有任务都已开始执行(而不是等待它们完成),那么我们可能需要采用不同的策略,比如使用
execute
方法结合其他同步机制(如
CountDownLatch
)。

3.使用
ExecutorService
来异步执行多个
Callable
任务方法示例

以下是一个详细完整的代码示例,该示例使用了
ExecutorService
来异步执行多个
Callable
任务,并通过遍历
Future
列表来等待所有任务完成并获取它们的结果。

import java.util.ArrayList;  
import java.util.List;  
import java.util.concurrent.*;  
  
public class ThreadPoolFutureCompleteExample {  
  
    public static void main(String[] args) {  
        // 创建一个包含固定数量线程的线程池  
        ExecutorService executorService = Executors.newFixedThreadPool(4);  
  
        // 创建一个Callable任务列表  
        List<Callable<String>> tasks = new ArrayList<>();  
        for (int i = 0; i < 10; i++) {  
            final int taskId = i;  
            tasks.add(new Callable<String>() {  
                @Override  
                public String call() throws Exception {  
                    // 模拟任务执行  
                    TimeUnit.SECONDS.sleep(1); // 假设每个任务需要1秒  
                    return "任务 " + taskId + " 完成";  
                }  
            });  
  
            // 或者使用Lambda表达式(如果你使用的是Java 8或更高版本)  
            // tasks.add(() -> {  
            //     TimeUnit.SECONDS.sleep(1);  
            //     return "任务 " + taskId + " 完成";  
            // });  
        }  
  
        try {  
            // 使用invokeAll提交所有任务,这将返回一个Future列表  
            List<Future<String>> futures = executorService.invokeAll(tasks);  
  
            // 遍历Future列表,获取每个任务的结果  
            for (Future<String> future : futures) {  
                // get()会阻塞,直到对应的任务完成  
                System.out.println(future.get());  
            }  
  
            // 关闭线程池  
            executorService.shutdown();  
  
            // 等待线程池中的所有线程都执行完毕(可选)  
            // 注意:由于我们已经调用了invokeAll并等待了所有Future的完成,这一步通常是多余的  
            // 但为了完整性,我还是展示了如何等待线程池关闭  
            boolean terminated = executorService.awaitTermination(60, TimeUnit.SECONDS);  
            if (!terminated) {  
                // 如果线程池没有在规定时间内关闭,则强制关闭  
                System.err.println("线程池没有在规定时间内关闭,尝试强制关闭...");  
                executorService.shutdownNow();  
                // 注意:shutdownNow()不保证已经提交的任务会被取消  
                // 它会尝试停止正在执行的任务,但已经开始执行的任务可能无法被中断  
            }  
  
        } catch (InterruptedException | ExecutionException e) {  
            // 处理异常  
            e.printStackTrace();  
  
            // 如果当前线程在等待过程中被中断,尝试关闭线程池  
            if (!executorService.isShutdown()) {  
                executorService.shutdownNow();  
            }  
  
            // 根据需要,可能还需要重新设置中断状态  
            Thread.currentThread().interrupt();  
        }  
    }  
}

在这个示例中,我使用了传统的匿名内部类来创建
Callable
任务(同时也提供了Lambda表达式的注释),以便与各种Java版本兼容。然而,如果我们正在使用Java 8或更高版本,我强烈推荐我们使用Lambda表达式来简化代码。

请注意,
invokeAll
方法会阻塞调用它的线程,直到所有任务都完成,或者直到等待超时(如果我们提供了超时时间)。但是,在这个示例中,我们没有为
invokeAll
提供超时时间,因此它会一直等待,直到所有任务都完成。

另外,请注意,在
catch
块中,如果捕获到
InterruptedException
,我们检查了线程池是否已经被关闭(使用
isShutdown
方法)。如果没有,我们调用
shutdownNow
方法来尝试关闭线程池并停止正在执行的任务。然而,需要注意的是,
shutdownNow
方法并不保证能够停止所有已经开始执行的任务,因为某些任务可能无法被中断。

最后,如果在捕获到
InterruptedException
后,我们确定当前线程需要被重新中断(比如,我们在一个循环中等待某个条件,而中断是用来退出循环的),那么我们应该调用
Thread.currentThread().interrupt()
来重新设置中断状态。在这个示例中,我们没有这样做,因为
main
方法不需要重新中断。但是,在更复杂的场景中,这可能是必要的。