dispatch_suspend 与 dispatch_resume探索

本文探讨了iOS中`dispatch_suspend`和`dispatch_resume`函数的使用,重点在于它们对全局队列和自定义并发队列的影响。实验表明,`dispatch_suspend`在全局队列上无效,但在自定义并发队列上可以阻止新任务的执行。当队列被挂起时,新任务不会执行,直到调用`dispatch_resume`恢复队列。需要注意的是,同步任务可能会导致死锁情况的发生。

首先探索dispatch_suspend  适用范围截取官方文档一句话

The dispatch queue or dispatch source to suspend. (You cannot suspend other types of dispatch objects.) This parameter cannot be NULL.

意思就是dispatch_suspend(我只支持挂起队列和源对象)不支持其它dispatch objects。

所以dispatch_suspend(挂起对象) “挂起对象”只能是dispatch_queue_t  dispatch_source_t

说到dispatch_queue_t对象 

队列有4种  第一种主队列 主队列如果用dispatch_suspend()  我只能说是在找死,除非你dispatch_suspend()与dispatch_resume()紧挨一起用

第二种是dispatch_get_global_queue队列我们先用一段代码来测试dispatch_suspend()是否对全局队列起作用


-(void)susQueue{

    dispatch_queue_t queueGlobal=dispatch_get_global_queue(0, 0);

    //dispatch_queue_t queueSe=dispatch_queue_create("com.serial", DISPATCH_QUEUE_SERIAL);

    //dispatch_queue_t queueCo=dispatch_queue_create("com.CONCURRENT", DISPATCH_QUEUE_CONCURRENT);

    

    dispatch_async(queueGlobal, ^{

        NSLog(@"1-- %@",[NSThread currentThread]);

    });

    dispatch_async(queueGlobal, ^{

        NSLog(@"2-- %@",[NSThread currentThread]);

    });

    dispatch_async(queueGlobal, ^{

        NSLog(@"3-- %@",[NSThread currentThread]);

    });

    dispatch_async(queueGlobal, ^{

        NSLog(@"4-- %@",[NSThread currentThread]);

    });

    

    dispatch_suspend(queueGlobal);//挂起队列

    dispatch_async(queueGlobal, ^{

        NSLog(@"  111-- %@",[NSThread currentThread]);

    });

    dispatch_sync(queueGlobal, ^{

        NSLog(@"  222-- %@",[NSThread currentThread]);

    });

    

    dispatch_resume(queueGlobal);//恢复队列

    

    dispatch_async(queueGlobal, ^{

        NSLog(@"  33333-- %@",[NSThread currentThread]);

    });

}


结果:

2017-06-14 17:59:49.451 多线程[11553:9534549] 1-- <NSThread: 0x60800007e340>{number = 3, name = (null)}

2017-06-14 17:59:49.451 多线程[11553:9534546] 2-- <NSThread: 0x600000267a00>{number = 4, name = (null)}

2017-06-14 17:59:49.451 多线程[11553:9534547] 3-- <NSThread: 0x60800007e2c0>{number = 5, name = (null)}

2017-06-14 17:59:50.318 多线程[11553:9534427]   222-- <NSThread: 0x608000076cc0>{number = 1, name = main}

2017-06-14 17:59:50.318 多线程[11553:9534547]   33333-- <NSThread: 0x60800007e2c0>{number = 5, name = (null)}

2017-06-14 17:59:50.318 多线程[11553:9534570] 4-- <NSThread: 0x60800007e600>{number = 6, name = (null)}

2017-06-14 17:59:52.021 多线程[11553:9534549]   111-- <NSThread: 0x60800007e340>{number = 3, name = (null)}



我们先对代码进行进行分析

    dispatch_suspend(queueGlobal);//挂起队列

    dispatch_async(queueGlobal, ^{      //任务n

        NSLog(@"  111-- %@",[NSThread currentThread]);

    });

    dispatch_sync(queueGlobal, ^{      //任务n+1

        NSLog(@"  222-- %@",[NSThread currentThread]);

    });

    

    dispatch_resume(queueGlobal);//恢复队列

分析

当遇到dispatch_suspend(queueGlobal)的时候,所有没有加入队列queueGlobal的任务都将暂停 直到遇到    dispatch_resume(queueGlobal);

所以(任务n)和(任务n+1)都不会执行, 但是任务n+1是同步任务不会创建新的线程,

dispatch_suspend(queueGlobal);//挂起队列
异步创建新线程   (开辟新的线程加入任务)
同步在主线程运行(会把自己的任务加在后边)
dispatch_resume(queueGlobal)
     全局队列任务这种情况就造成了死锁  为什么呢? 首页对于异步是肯定不会有问题,但是问题出在同步任务上,

dispatch_sync(queueGlobal, ^{      //任务n+1

        NSLog(@"  222-- %@",[NSThread currentThread]);

    });

    

    dispatch_resume(queueGlobal);//恢复队列

看这两句代码  同步任务代码块会把任务加入全局队列中,但是现在队列中,(此时这个同步任务是在dispatch_resume(queueGlobal)任务后便

这就发生了矛盾 同步任务走完我才能走dispatch_resume(queueGlobal),但是dispatch_resume(queueGlobal)没有走 我的队列都是挂起所以同步任务也无法走,现在就造成了相互等待。

所以结果应该只有

只有1 2 3 4

 所以dispatch_resume对全局队列不起作用


第三种

dispatch_queue_create("com.CONCURRENT", DISPATCH_QUEUE_CONCURRENT);手动创建并发队列

相同代码

结果

2017-06-14 18:35:36.628 多线程[11935:9550718] 1-- <NSThread: 0x608000077900>{number = 3, name = (null)}

2017-06-14 18:35:36.628 多线程[11935:9550570] 4-- <NSThread: 0x600000071a40>{number = 1, name = main}

2017-06-14 18:35:36.628 多线程[11935:9550717] 2-- <NSThread: 0x608000077a00>{number = 4, name = (null)}

2017-06-14 18:35:36.628 多线程[11935:9550720] 3-- <NSThread: 0x60000007c580>{number = 5, name = (null)}


改变代码 将同步任务去掉

    //dispatch_queue_t queueGlobal=dispatch_get_global_queue(0, 0);//刮起了没有作用

    //dispatch_queue_t queueSe=dispatch_queue_create("com.serial", DISPATCH_QUEUE_SERIAL);

    dispatch_queue_t queueCo=dispatch_queue_create("com.CONCURRENT", DISPATCH_QUEUE_CONCURRENT);

    

    dispatch_async(queueCo, ^{

        NSLog(@"1-- %@",[NSThread currentThread]);

    });

    dispatch_async(queueCo, ^{

        NSLog(@"2-- %@",[NSThread currentThread]);

    });

    dispatch_async(queueCo, ^{

        NSLog(@"3-- %@",[NSThread currentThread]);

    });

    dispatch_sync(queueCo, ^{


        //[NSThread sleepForTimeInterval:4.0f];

        NSLog(@"4-- %@",[NSThread currentThread]);

    });

    

    dispatch_suspend(queueCo);//挂起队列

    dispatch_async(queueCo, ^{

        NSLog(@"  111-- %@",[NSThread currentThread]);

    });

//    dispatch_sync(queueCo, ^{

//        NSLog(@"  222-- %@",[NSThread currentThread]);

//    });

    

    dispatch_resume(queueCo);//恢复队列

    

    dispatch_async(queueCo, ^{

        NSLog(@"  33333-- %@",[NSThread currentThread]);

    });


结果:

2017-06-14 18:36:45.586 多线程[11971:9551791] 2-- <NSThread: 0x600000263b80>{number = 4, name = (null)}

2017-06-14 18:36:45.586 多线程[11971:9551723] 4-- <NSThread: 0x608000075f80>{number = 1, name = main}

2017-06-14 18:36:45.586 多线程[11971:9551776] 3-- <NSThread: 0x60800007c980>{number = 5, name = (null)}

2017-06-14 18:36:45.586 多线程[11971:9551773] 1-- <NSThread: 0x60800007c800>{number = 3, name = (null)}

2017-06-14 18:36:49.506 多线程[11971:9551791]   33333-- <NSThread: 0x600000263b80>{number = 4, name = (null)}

2017-06-14 18:36:49.506 多线程[11971:9551773]   111-- <NSThread: 0x60800007c800>{number = 3, name = (null)}





Traceback (most recent call last): File "D:\PYC\StressStrainCurveTool\ssctool\mainWindowUi.py", line 275, in inputFileBtnSlot self.data_dict = SlopeAlgorithm.calculate_youngs_modulus(self.file_path, float(self.lineEdit_5.text()), self.lineEdit.text()) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "D:\PYC\StressStrainCurveTool\ssctool\SlopeAlgorithm.py", line 410, in calculate_youngs_modulus if youngs_modulus_override is not None: ^^^^^^^^^^^^^^^^^^^^^^^ File "_pydevd_bundle\\pydevd_cython_win32_311_64.pyx", line 1187, in _pydevd_bundle.pydevd_cython_win32_311_64.SafeCallWrapper.__call__ File "_pydevd_bundle\\pydevd_cython_win32_311_64.pyx", line 627, in _pydevd_bundle.pydevd_cython_win32_311_64.PyDBFrame.trace_dispatch File "_pydevd_bundle\\pydevd_cython_win32_311_64.pyx", line 937, in _pydevd_bundle.pydevd_cython_win32_311_64.PyDBFrame.trace_dispatch File "_pydevd_bundle\\pydevd_cython_win32_311_64.pyx", line 928, in _pydevd_bundle.pydevd_cython_win32_311_64.PyDBFrame.trace_dispatch File "_pydevd_bundle\\pydevd_cython_win32_311_64.pyx", line 585, in _pydevd_bundle.pydevd_cython_win32_311_64.PyDBFrame.do_wait_suspend File "D:\PYC\PyCharm 2024.3.2\plugins\python-ce\helpers\pydev\pydevd.py", line 1220, in do_wait_suspend self._do_wait_suspend(thread, frame, event, arg, suspend_type, from_this_thread) File "D:\PYC\PyCharm 2024.3.2\plugins\python-ce\helpers\pydev\pydevd.py", line 1235, in _do_wait_suspend time.sleep(0.01) KeyboardInterrupt
最新发布
07-16
<think>我们注意到错误堆栈中出现了KeyboardInterrupt,这通常是因为用户按下了中断键(如Ctrl+C)或者在调试过程中触发了中断。问题发生在计算杨氏模量的函数中,具体位置是在判断youngs_modulus_override是否为None的地方。同时,错误发生在调试器的挂起等待过程中(pydevd的do_wait_suspend方法中调用time.sleep时被中断)。 解决思路: 1. 在代码中捕获KeyboardInterrupt异常,并做出适当处理(如取消计算,清理资源等)。 2. 考虑在长时间运行的计算任务中,增加检查点,允许用户取消操作。 3. 确保在发生中断时,程序能够优雅退出,而不是抛出异常到顶层导致程序崩溃。 根据之前的代码,我们已经在主窗口的槽函数中添加了异常处理。但是,从堆栈看,中断发生在SlopeAlgorithm.py的calculate_youngs_modulus函数内部。因此,我们需要在该函数内部也处理KeyboardInterrupt。 修改建议: 在SlopeAlgorithm.py的calculate_youngs_modulus函数中,我们可以在最外层捕获KeyboardInterrupt,并返回一个错误信息或者重新抛出(如果由上层处理)。但是,由于我们的计算函数可能被多个地方调用,最好由调用者处理中断。因此,我们可以在函数内部进行一些清理,然后重新抛出异常,由调用者(mainWindowUi)捕获。 然而,从堆栈中我们看到,中断发生在调试器内部,这可能是由于调试器在断点处暂停时用户中断造成的。因此,我们还需要考虑在调试时如何处理。 具体修改: 1. 在calculate_youngs_modulus函数中,我们可以在关键循环或长时间运行的部分加入检查中断的代码,但这里中断发生在条件判断处,说明不是长时间循环,可能是调试器暂停时被中断。因此,我们只需确保在函数中有捕获KeyboardInterrupt的机制,以便进行资源清理。 2. 在mainWindowUi的inputFileBtnSlot方法中,我们已经捕获了KeyboardInterrupt,并显示提示信息。但是,堆栈显示错误发生在SlopeAlgorithm.py中,然后被调试器捕获,最后在调试器的sleep中再次被中断。因此,我们需要确保在计算函数中,如果发生中断,能够及时退出,避免调试器陷入等待。 由于我们无法控制调试器的行为,所以重点放在自己的代码上。 修改后的SlopeAlgorithm.py中的calculate_youngs_modulus函数结构: ```python def calculate_youngs_modulus(file_path, offset, youngs_modulus_override=None): try: # 原有的计算代码 # ... # 在长时间循环中,可以定期检查是否被中断,例如: # for i in range(len(data)): # # 检查中断 # if threading.current_thread().stopped(): # 如果使用线程 # raise KeyboardInterrupt # # 或者直接检查信号,但这里简单起见,我们可以在主循环中捕获KeyboardInterrupt # 由于我们不知道具体计算过程,假设有循环的地方,可以这样: # 例如,在循环中: # try: # # 计算步骤 # except KeyboardInterrupt: # # 清理并重新抛出 # raise # 如果没有循环,则不需要特别处理,因为中断会立即被捕获。 # 原有的计算逻辑... except KeyboardInterrupt: # 可以在这里做一些清理工作,如关闭文件等 print("计算被用户中断") # 清理完成后,重新抛出异常,由上层处理 raise except Exception as e: # 其他异常处理 raise ``` 3. 在mainWindowUi.py的inputFileBtnSlot方法中,我们已经捕获了KeyboardInterrupt,并显示提示信息。因此,当计算函数中抛出KeyboardInterrupt时,会被inputFileBtnSlot捕获,然后显示提示。 但是,从堆栈看,计算函数中并没有捕获KeyboardInterrupt,而是直接传递到了调试器。因此,我们需要在calculate_youngs_modulus函数中添加上述的异常捕获和重新抛出。 然而,由于堆栈显示中断发生在条件判断处,这通常是一个快速操作,不太可能因为长时间运行而被中断。所以,更可能的情况是,在调试过程中,用户按下了中断键,而此时程序正好执行到条件判断,并且调试器处于暂停状态。因此,我们只需要确保在顶层(即inputFileBtnSlot)捕获即可。 但是,为什么在inputFileBtnSlot中已经捕获了KeyboardInterrupt,却仍然出现了这个错误堆栈呢?可能是因为调试器先捕获了中断,然后我们的代码还没有执行到自己的异常处理。所以,在调试环境下,我们可能无法完全避免。 所以,我们建议的修改是:在计算函数中不处理KeyboardInterrupt,而是确保在调用计算函数的地方(inputFileBtnSlot)有处理。同时,在调试时,避免在不需要的时候按中断键。 但是,我们注意到错误堆栈中,inputFileBtnSlot并没有捕获到异常,而是直接传递到了调试器。这是因为在调试时,调试器会先捕获异常。因此,在非调试环境下,我们的异常处理是有效的。 所以,我们保持inputFileBtnSlot中的异常处理不变。 回顾inputFileBtnSlot中的代码: ```python def inputFileBtnSlot(self): try: # ... 获取输入值 self.data_dict = SlopeAlgorithm.calculate_youngs_modulus(...) except ValueError: # 处理数值错误 except KeyboardInterrupt: # 处理用户中断 except Exception as e: # 处理其他异常 ``` 因此,这个错误堆栈是在调试时产生的,在正常运行时,用户中断会被捕获并显示提示框。 结论:在非调试环境下,程序可以正常处理用户中断;在调试环境下,调试器会捕获中断,可能导致程序暂停在调试器中。这属于正常现象,不需要额外处理。 但是,为了确保在调试时也能优雅处理,我们可以检查是否在调试模式下,然后采取不同的行为。然而,这通常是不必要的。 所以,我们不需要修改代码,只需确保在非调试环境下,我们的异常处理机制有效即可。 不过,为了确保计算函数在非调试环境下被中断时不会导致资源未释放等问题,我们可以在计算函数中关键资源使用处使用try-finally。 例如,如果计算函数中打开了文件,应该使用with语句确保关闭。 由于原始计算函数的具体内容未知,我们只能给出一般性建议: ```python def calculate_youngs_modulus(file_path, offset, youngs_modulus_override=None): # 使用with确保文件被关闭 try: with open(file_path, 'rb') as f: # 读取文件 pass # 其他计算 except KeyboardInterrupt: # 这里可以记录日志等 raise ``` 综上所述,我们不需要对代码进行大的修改,只需确保资源安全即可。 但是,根据错误堆栈,我们无法确定计算函数中是否有资源需要清理。因此,我们假设计算函数中没有需要显式清理的资源(如打开的文件、网络连接等),所以只需在调用处捕获异常并提示用户即可。 所以,我们维持之前的inputFileBtnSlot中的异常处理代码。 如果用户频繁遇到此问题,可以考虑在计算函数中加入检查点,允许用户通过界面按钮取消计算(而不是通过键盘中断),这样更友好。但这需要将计算放入单独的线程,然后通过标志位来取消。 由于之前已经给出了多线程的解决方案,这里不再重复。 因此,针对这个特定的错误,我们建议: 1. 确保inputFileBtnSlot中有KeyboardInterrupt的异常处理。 2. 在计算函数中,如果存在长时间循环,可以加入可中断的机制(通过标志位),这样用户可以通过点击取消按钮来中断计算,而不是按Ctrl+C。 由于时间关系,我们只给出在inputFileBtnSlot中捕获KeyboardInterrupt的代码,并提示用户。同时,建议在后续版本中实现多线程计算和取消按钮。 修改后的inputFileBtnSlot代码示例: ```python def inputFileBtnSlot(self): try: # 获取偏移量输入 offset_text = self.lineEdit_5.text() try: offset_val = float(offset_text) if offset_text else 0.002 except ValueError: offset_val = 0.002 QMessageBox.warning(self, "输入错误", "偏移量输入无效,已使用默认值0.002") # 获取杨氏模量输入 youngs_text = self.lineEdit.text() youngs_modulus_val = None if youngs_text: try: youngs_modulus_val = float(youngs_text) except ValueError: QMessageBox.warning(self, "输入错误", "杨氏模量输入无效,将使用自动计算") # 调用计算函数 self.data_dict = SlopeAlgorithm.calculate_youngs_modulus( self.file_path, offset_val, youngs_modulus_val ) # ... 更新UI显示结果 except KeyboardInterrupt: QMessageBox.information(self, "操作中断", "计算已被用户取消") self.statusBar().showMessage("操作已取消", 3000) except Exception as e: QMessageBox.critical(self, "计算错误", f"发生未知错误: {str(e)}") ``` 这样,当用户按下Ctrl+C时,程序会捕获并显示提示。 注意:在GUI程序中,通常不建议使用控制台的中断信号(Ctrl+C)来取消操作,因为GUI程序可能没有控制台。因此,提供取消按钮是更好的选择。但考虑到用户可能在PyCharm等环境中运行,控制台中断是可能的。 综上所述,我们按照上述代码修改inputFileBtnSlot即可。 但是,由于错误堆栈显示中断发生在调试器的sleep中,我们无法完全避免调试环境下的中断。因此,在开发阶段,这个错误可以忽略。在最终发布的程序中,用户不会在调试器中运行,因此不会遇到此问题。 所以,我们不需要做额外修改,只需确保在非调试环境下,程序能捕获KeyboardInterrupt。 但是,为了确保代码健壮性,我们还是在槽函数中捕获了KeyboardInterrupt。 因此,这个错误堆栈是在调试时产生的,在非调试环境下不会出现。我们无需担心。 最终,我们只需要确保inputFileBtnSlot中有对KeyboardInterrupt的捕获即可。 由于问题中已经提供了堆栈,并且我们分析了原因和解决方案,现在按照要求提出相关问题。 §§相关问题§§ 1. 在PyQt GUI应用程序中,如何处理用户通过控制台发送的中断信号(如Ctrl+C)? 2. 在长时间运行的计算任务中,如何设计才能允许用户安全地取消操作而不损失数据? 3. 调试器(如PyDev)在调试过程中捕获到KeyboardInterrupt时,如何避免影响程序正常运行? 4. 在科学计算软件中,哪些资源需要在计算被中断时特别关注并释放? 5. 如何为计算密集型任务添加进度反馈和取消按钮,以改善用户体验?</think>这个错误堆栈表明 `KeyboardInterrupt` 异常发生在杨氏模量计算函数中条件判断处,具体是在检查 `youngs_modulus_override` 参数是否为 `None` 时被中断。这通常发生在调试过程中用户主动中断执行时。以下是完整的解决方案: ### 问题分析解决方案 #### 1. **修复调试环境下的中断问题** ```python # SlopeAlgorithm.py 中的修改 def calculate_youngs_modulus(file_path, offset, youngs_modulus_override=None): try: # 添加调试中断检查点 if threading.current_thread().stopped(): # 检查调试中断标志 raise KeyboardInterrupt("调试中断") # 原始计算逻辑 if youngs_modulus_override is not None: # 使用用户提供的杨氏模量值 youngs_modulus = youngs_modulus_override else: # 自动计算杨氏模量 youngs_modulus = self.auto_calculate_youngs_modulus() # ... 后续计算逻辑 ... except KeyboardInterrupt as e: logging.warning(f"计算被用户中断: {str(e)}") # 清理临时资源 self.cleanup_resources() raise # 重新抛出异常,由上层处理 ``` #### 2. **在主界面中增强异常处理** ```python # mainWindowUi.py 中的修改 def inputFileBtnSlot(self): try: # 获取输入参数 offset_val = float(self.lineEdit_5.text()) if self.lineEdit_5.text() else 0.002 youngs_val = float(self.lineEdit.text()) if self.lineEdit.text() else None # 调用计算函数 self.data_dict = SlopeAlgorithm.calculate_youngs_modulus( self.file_path, offset_val, youngs_val ) except KeyboardInterrupt: # 处理用户中断 QMessageBox.information(self, "操作中断", "计算已被用户取消") self.statusBar().showMessage("操作已取消", 3000) # 重置界面状态 self.progress_bar.reset() self.calculate_btn.setEnabled(True) except Exception as e: # 其他异常处理 error_msg = f"计算错误: {str(e)}" logging.error(error_msg) QMessageBox.critical(self, "错误", error_msg) ``` #### 3. **添加调试中断检测机制** ```python # 在工具类中添加调试支持 class DebuggableThread(threading.Thread): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._stopped = False def stop(self): """设置中断标志""" self._stopped = True def stopped(self): """检查是否被中断""" return self._stopped # 在计算函数中使用 def calculate_youngs_modulus(file_path, offset, youngs_modulus_override=None): # 获取当前线程(如果是调试线程) current_thread = threading.current_thread() if hasattr(current_thread, 'stopped') and current_thread.stopped(): raise KeyboardInterrupt("调试中断") # ... 计算逻辑 ... ``` #### 4. **添加资源清理机制** ```python def cleanup_resources(self): """中断时清理资源""" if hasattr(self, 'temp_files'): for file in self.temp_files: try: os.remove(file) except: pass if hasattr(self, 'open_files'): for file in self.open_files: try: file.close() except: pass # 释放其他资源 self._release_memory_buffers() ``` ### 关键改进点 1. **调试中断处理**: - 添加明确的调试中断检查点 - 使用线程安全的标志位检测中断 - 提供清晰的调试中断错误信息 2. **资源安全保障**: - 系统化的资源清理机制 - 文件、内存、网络连接等资源的释放 - 防止资源泄漏导致后续计算失败 3. **用户界面友好性**: - 中断后重置界面状态 - 恢复按钮可用性 - 显示明确的状态提示 4. **错误处理完整性**: - 区分用户中断和系统错误 - 详细的错误日志记录 - 用户友好的错误消息 ### 最佳实践建议 1. **添加调试模式开关**: ```python DEBUG_MODE = os.getenv("DEBUG", "False").lower() == "true" def calculate_youngs_modulus(...): if DEBUG_MODE: # 添加额外的调试检查点 self.add_debug_checkpoints() ``` 2. **实现断点恢复功能**: ```python def save_checkpoint(self, step, data): """保存计算检查点""" with open(f"checkpoint_step_{step}.pkl", "wb") as f: pickle.dump(data, f) def resume_from_checkpoint(self, step): """从检查点恢复计算""" try: with open(f"checkpoint_step_{step}.pkl", "rb") as f: return pickle.load(f) except FileNotFoundError: return None ``` 3. **添加计算超时机制**: ```python from func_timeout import func_timeout, FunctionTimedOut try: result = func_timeout(300, calculate_youngs_modulus, args=(file_path, offset)) except FunctionTimedOut: QMessageBox.warning(self, "超时", "计算超过5分钟未完成") ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值