Python程序暂停 恢复运行功能 多线程暂停 恢复

部署运行你感兴趣的模型镜像

        开始我想暂停程序,可以点击控制台来暂停阻塞。稍后在Crl+C来停止, 后来我使用了多线程threading.Thread() 发现点击控制台难以实现类似需求了。 原生的Thread() 并没有提供 暂停 恢复(继续)  的功能, 我利用windows api实现了 外部对线程进行暂停恢复。

from threading import Thread
import time


def work():
    for i in range(20):
        time.sleep(1)
        print(f"第{i}次循环")


thread_obj = Thread(target=work)
thread_obj.start()

我先这样启动了线程,我曾尝试用windows32 api 控制他, 结果!! 不行!!! 这里的thread_obj 里面保存都是一个Thread内部的操作对象。是Thread概念的线程id。 

不是系统级别的线程id。 然后我获取了系统级别的真正的线程id。 再把他交给windows api 去操作, 结果就可以了, 如果想这样实现就要重新 构造Thread,我写了一个新类来继承Thread。下面我整理了一个模块

import inspect
import threading
import ctypes
import time
from ctypes import wintypes
import os

# Windows API 定义
THREAD_SUSPEND_RESUME = 0x0002
THREAD_QUERY_INFORMATION = 0x0040
WAIT_TIMEOUT = 0x00000102
WAIT_OBJECT_0 = 0x00000000
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)

OpenThread = kernel32.OpenThread
OpenThread.argtypes = [wintypes.DWORD, wintypes.BOOL, wintypes.DWORD]
OpenThread.restype = wintypes.HANDLE

SuspendThread = kernel32.SuspendThread
SuspendThread.argtypes = [wintypes.HANDLE]
SuspendThread.restype = wintypes.DWORD

ResumeThread = kernel32.ResumeThread
ResumeThread.argtypes = [wintypes.HANDLE]
ResumeThread.restype = wintypes.DWORD

WaitForSingleObject = kernel32.WaitForSingleObject
WaitForSingleObject.argtypes = [wintypes.HANDLE, wintypes.DWORD]
WaitForSingleObject.restype = wintypes.DWORD

CloseHandle = kernel32.CloseHandle



class ThreadEx(threading.Thread):
    def __init__(self, target=None, args=(), start=True, kwargs=None, group=None,  name=None, daemon=True):
        """
             self.tid: 系统级别的id 这个 类创建的 对象
                 python thread类对象,通常也称为 线程id 他是 thread操作的句柄对象。 这里我在执行thread对象时候执行获取了线程在系统里面的id
                 然后通过系统api 根据 系统里面的线程id来控制线程的 暂停 停止 继续,结束 。这里对于默认的Thread 线程类的继承 完全继承了6个初始化对象
                 的参数: group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None
            通过 self.status  记录线程的状态。
            这个类适合 创建 操作 单个线程, 如果需要多个线程同时操作,可以使用 ThreadManage 类来实现更高级的功能。
        """
        if os.name != 'nt':
            raise NotImplementedError("当前仅支持 Windows 平台")

        self.status = ''
        self.target_function = target
        self.tid = None
        #  守护线程。  daemon=True
        super().__init__(target=self.__target_function, args=args, kwargs=kwargs, group=group, name=name, daemon=daemon)
        # 启动线程 默认True
        if start:
            self.status = 'running'
            try:
                self.start()
            except Exception as e:
                self.status = 'stopped'
                print(f'线程start 操作出现错误:{e}')
        # 如果不想直接启动线程,也可以仅创建一个线程对象,返回 被继承后的 Thread 对象, 拥有全部Thread原本方法和属性 和新增的方法。

    def suspend(self):
        """ 调用系统api 暂停线程"""
        try:
            h_thread = OpenThread(THREAD_SUSPEND_RESUME, False, self.tid)
            if h_thread:
                SuspendThread(h_thread)
                CloseHandle(h_thread)
            self.status = "suspended"
        except Exception as e:
            pass

    def resume(self):
        """ 调用系统api 恢复线程"""
        try:
            h_thread = OpenThread(THREAD_SUSPEND_RESUME, False, self.tid)
            if h_thread:
                ResumeThread(h_thread)
                CloseHandle(h_thread)
            self.status = "running"
        except Exception as e:
            pass

    def stop(self, try_times=1, message=''):
        """ 强制停止线程 返回布尔值 和 错误信息"""
        result = self.__class__.stop_thread(self.tid, try_times=try_times, message=message )
        if result:
            self.status = 'stopped'
        return result

    @classmethod
    def stop_thread(cls, target_thread_id,  try_times=2,  message=''):
        result = False
        for i in range(try_times):
            try:
                result = cls._async_raise(target_thread_id, SystemExit)
                if result:
                    if message:
                        print(f'线程 {message} 停止成功')
                    break
            except Exception as e:
                result = False
                time.sleep(3)
        return result

    def __target_function(self, *args, **kwargs):
        """
            里用__target_function 调用 target_function 在调用之前这样不需要修改 原版的 thread库,可以再调用之前。
            # 准确获取到系统级的 线程id
            这里完全复刻了 子类的 参数传递。
        """
        try:
            self.tid = ctypes.windll.kernel32.GetCurrentThreadId()
            self.target_function(*args, **kwargs)
        finally:
            self.status = 'stopped'

    @classmethod
    def _async_raise(cls, tid, exc_type):
        """ 尝试根据线程 id 强制停止 线程 返回布尔值 是否成功"""
        try:
            """raises the exception, performs cleanup if needed"""
            tid = ctypes.c_long(tid)
            if not inspect.isclass(exc_type):
                exc_type = type(exc_type)
            res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exc_type))
            if res == 0:
                raise ValueError("invalid thread id")
            elif res != 1:
                ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
                raise SystemError("PyThreadState_SetAsyncExc failed")
            return True
        except Exception as e:
            pass
        return False

    def get_status(self):
        """
            使用 Windows API 判断线程是否仍在运行(未终止)。
            注意:挂起的线程也会被认为是“仍在运行”。
            实际应用中 有待测试
        """
        if not self.tid:
            self.status = ''
            return self.status  # 线程尚未启动
        h_thread = OpenThread(THREAD_QUERY_INFORMATION, False, self.tid)
        if h_thread and h_thread != 0:
            result = WaitForSingleObject(h_thread, 0)
            CloseHandle(h_thread)
            if result == WAIT_TIMEOUT and self.status != "suspended":
                self.status = 'running'
            elif result == WAIT_OBJECT_0:
                self.status = 'stopped'
        else:
            self.status = 'stopped'
        return self.status

class ThreadManage:
    """
        调用 包含更多功能的 ThreadEx 类。 创建对象 线程, 并记录线程a
        这个类适合在需要管理多个线程时候使用, 可以停止全部线程,暂停全部线程,恢复全部线程运行,
    """
    _lock = threading.Lock()
    thread_list = []  # 全局线程列表

    def __init__(self):
        pass

    @classmethod
    def create_thread(cls, target, start=True, args=(), kwargs=None, group=None,  name=None, daemon=True):
        cls.cleanup()
        with cls._lock:
            thread_id = ThreadEx(target=target, start=start, args=args,
                                kwargs=kwargs, group=group, name=name, daemon=daemon)
            ThreadManage.thread_list.append(thread_id)  # 添加到全局列表
            return thread_id

    @classmethod
    def suspend_all(cls):
        """ 暂停全部线程 进入休眠状态"""
        cls.cleanup()
        for t in cls.thread_list:
            t.suspend()

    @classmethod
    def resume_all(cls):
        """ 恢复全部线程 从休眠状态中"""
        cls.cleanup()
        for t in cls.thread_list:
            t.resume()

    @classmethod
    def stop_all(cls):
        cls.cleanup()
        """ 强制停止 全部线程"""
        for index in range(len(cls.thread_list)-1, -1, -1):
            if cls.thread_list[index].stop():
                cls.thread_list.pop(index)

    @classmethod
    def stop(cls, t):
        cls.cleanup()
        index_ = -1
        for index in range(len(cls.thread_list)):
            if cls.thread_list[index] == t:
                index_ = index
                break
        if t.stop():
            cls.thread_list.pop(index_)

    @classmethod
    def cleanup(cls):
        with cls._lock:
            cls.thread_list = [t for t in cls.thread_list if t.status != 'stopped']

    @classmethod
    def update_thread_status(cls):
        """ 动态更新线程状态,更准确的更新线程状态 """
        for t in cls.thread_list:
            t.get_status()
        cls.cleanup()


def test_worker(startwith):
    count = 0
    for ii in range(5):
        print(f"{startwith} 运行: {count}")
        count += 1
        time.sleep(1)
        if ii == 10:
            exit()
            # raise Exception('adqweqrqh')
    time.sleep(1)
    print("运行结束")


if __name__ == '__main__':
    t = ThreadManage.create_thread(target=test_worker, args=('子线程 1',))
    # t2 = ThreadManage.create_thread(target=test_worker, args= ('子线程 2', ))
    # # print(f't.tid={t.tid}')
    # time.sleep(3)
    # print("暂停线程 1")
    # ThreadManage.suspend_all()  # 暂停

    # ThreadManage.stop(t)
    # time.sleep(3)
    # print("恢复线程")
    # # t.resume()   # 恢复
    #
    # time.sleep(3)
    # t.stop()
    count = 0
    while True:
        print(f"主线程: Running: {count}")
        print('t.status=', t.status)
        # print('t.get_status()=', t2.get_status())
        count += 1
        time.sleep(1)

       1  然后有了 ThreadEx基础之上, 我又新增了ThreadManage, 当然有了ThreadEx,  ThreadManage是轻而易举的了,ThreadManage来实现批量管理线程,

       2 在ThreadEx类里面增加了线程状态维护,静态的线程属性不一定可靠, 所以我又引入了一个动态判断线程状态的方法。 顺便我又增加了一个强制外部停止线程的功能。 

       3  通过 ThreadEx 类也可以调用原生的方法。ThreadEx 类 初始化时候完整复刻的原生Thread的参数。 类的创建时候并没有强制执行start, 而是默认start=True。 可以先创建后启动。

       4 上面的代码就是最新版的了。 

       5 目前在我的应用场景下使用没有问题, 需要的小伙伴们可以拿去用吧!!

       6 你也可以这样来:

        6.1 你之前代码可能是类似这样:

from threading improt Thread
def work_task_one():
    print('task one 函数被调用')


thread_id = Thread(target=work_task_one)
# 忽略...


# 当你需要暂停 恢复 强制停止线程...

        6.2 现在你可以这样:

# from threading improt Thread 
from newThread improt ThreadEx as Thread


def work_task_one():
    print('task one 函数被调用')


thread_id = Thread(target=work_task_one)
# 忽略...


# 当你需要暂停 恢复 强制停止线程...
thread_id.suspend()
"""
    适当的时候在恢复, 可以有gui界面按键的事件控制。
    thread_id.resume()
"""
# 然后在ThreadEx类中把start=True修改为默认值False
# 不影响其他位置 对于 标准的类 Thread  的使用, 因为 ThreadEx是继承了Thread 

感兴趣的朋友评论区聊聊吧!!! 我想听听你们怎么看这个代码!!

您可能感兴趣的与本文相关的镜像

Python3.9

Python3.9

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值