__try,__except,__finally,__leave

本文深入探讨Windows平台上的SEH异常处理模型,包括try-except语法的应用、异常捕获与处理流程,以及如何利用API获取异常信息进行分类处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

__try,__except,__finally,__leave

导读: 
从本篇文章开始,将全面阐述__try,__except,__finally,__leave异常模型机制,它也即是Windows系列操作系统平台上提供的SEH模型。主人公阿愚将在这里与大家分享SEH的学习过程和经验总结。 

  SEH有两项非常强大的功能。当然,首先是异常处理模型了,因此,这篇文章首先深入阐述SEH提供的异常处理模型。另外,SEH还有一个特别强大的功能,这将在下一篇文章中进行详细介绍。 

try-except入门

  SEH的异常处理模型主要由try-except语句来完成,它与标准C++所定义的异常处理模型非常类似,也都是可以定义出受监控的代码模块,以及定义异常处理模块等。还是老办法,看一个例子先,代码如下:

//seh-test.c
#include

 

void main()
{
puts("hello");
// 定义受监控的代码模块
__try
{
puts("in try");
}
//定义异常处理模块
__except(1)
{
puts("in except");
}
puts("world");
}

 

  呵呵!是不是很简单,而且与C++异常处理模型很相似。当然,为了与C++异常处理模型相区别,VC编译器对关键字做了少许变动。首先是在每个关键字加上两个下划线作为前缀,这样既保持了语义上的一致性,另外也尽最大可能来避免了关键字的有可能造成名字冲突而引起的麻烦等;其次,C++异常处理模型是使用catch关键字来定义异常处理模块,而SEH是采用__except关键字来定义。并且,catch关键字后面往往好像接受一个函数参数一样,可以是各种类型的异常数据对象;但是__except关键字则不同,它后面跟的却是一个表达式(可以是各种类型的表达式,后面会进一步分析)。

try-except进阶

  与C++异常处理模型很相似,在一个函数中,可以有多个try-except语句。它们可以是一个平面的线性结构,也可以是分层的嵌套结构。例程代码如下:

 

// 例程1
// 平面的线性结构
#include

 

void main()
{
puts("hello");
__try
{
puts("in try");
}
__except(1)
{
puts("in except");
}

 

// 又一个try-except语句
__try
{
puts("in try");
}
__except(1)
{
puts("in except");
}

 

puts("world");
}

 


// 例程2
// 分层的嵌套结构
#include

 

void main()
{
puts("hello");
__try
{
puts("in try");
// 又一个try-except语句
__try
{
puts("in try");
}
__except(1)
{
puts("in except");
}
}
__except(1)
{
puts("in except");
}

 

puts("world");
}

 

// 例程3
// 分层的嵌套在__except模块中
#include

 

void main()
{
puts("hello");
__try
{
puts("in try");
}
__except(1)
{
// 又一个try-except语句
__try
{
puts("in try");
}
__except(1)
{
puts("in except");
}

 

puts("in except");
}

 

puts("world");
}
1. 受监控的代码模块被执行(也即__try定义的模块代码);
  2. 如果上面的代码执行过程中,没有出现异常的话,那么控制流将转入到__except子句之后的代码模块中;
  3. 否则,如果出现异常的话,那么控制流将进入到__except后面的表达式中,也即首先计算这个表达式的值,之后再根据这个值,来决定做出相应的处理。这个值有三种情况,如下:
  EXCEPTION_CONTINUE_EXECUTION (–1) 异常被忽略,控制流将在异常出现的点之后,继续恢复运行。
  EXCEPTION_CONTINUE_SEARCH (0) 异常不被识别,也即当前的这个__except模块不是这个异常错误所对应的正确的异常处理模块。系统将继续到上一层的try-except域中继续查找一个恰当的__except模块。
  EXCEPTION_EXECUTE_HANDLER (1) 异常已经被识别,也即当前的这个异常错误,系统已经找到了并能够确认,这个__except模块就是正确的异常处理模块。控制流将进入到__except模块中。

 
try-except深入

  上面的内容中已经对try-except进行了全面的了解,但是有一点还没有阐述到。那就是如何在__except模块中获得异常错误的相关信息,这非常关键,它实际上是进行异常错误处理的前提,也是对异常进行分层分级别处理的前提。可想而知,如果没有这些起码的信息,异常处理如何进行?因此获取异常信息非常的关键。Windows提供了两个API函数,如下:

 

LPEXCEPTION_POINTERS GetExceptionInformation(VOID);
DWORD GetExceptionCode(VOID);

  其中GetExceptionCode()返回错误代码,而GetExceptionInformation()返回更全面的信息,看它函数的声明,返回了一个LPEXCEPTION_POINTERS类型的指针变量。那么EXCEPTION_POINTERS结构如何呢?如下,

 

typedef struct _EXCEPTION_POINTERS { // exp 
PEXCEPTION_RECORD ExceptionRecord; 
PCONTEXT ContextRecord; 
} EXCEPTION_POINTERS;

 

  呵呵!仔细瞅瞅,这是不是和上一篇文章中,用户程序所注册的异常处理的回调函数的两个参数类型一样。是的,的确没错!其中EXCEPTION_RECORD类型,它记录了一些与异常相关的信息;而CONTEXT数据结构体中记录了异常发生时,线程当时的上下文环境,主要包括寄存器的值。因此有了这些信息,__except模块便可以对异常错误进行很好的分类和恢复处理。不过特别需要注意的是,这两个函数只能是在__except后面的括号中的表达式作用域内有效,否则结果可能没有保证(至于为什么,在后面深入分析异常模型的实现时候,再做详细阐述)。看一个例程吧!代码如下:

 

#include 
#include

 

int exception_access_violation_filter(LPEXCEPTION_POINTERS p_exinfo)
{
if(p_exinfo->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
{
printf("存储保护异常\n");
return 1;
}
else return 0;
}

 

int exception_int_divide_by_zero_filter(LPEXCEPTION_POINTERS p_exinfo)
{
if(p_exinfo->ExceptionRecord->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO)
{
printf("被0除异常\n");
return 1;
}
else return 0;
}

 

void main()
{
puts("hello");
__try
{
__try
{
int* p;

 

// 下面将导致一个异常
p = 0;
*p = 45;
}
// 注意,__except模块捕获一个存储保护异常
__except(exception_access_violation_filter(GetExceptionInformation()))
{
puts("内层的except块中");
}
}
// 注意,__except模块捕获一个被0除异常
__except(exception_int_divide_by_zero_filter(GetExceptionInformation())) 
{
puts("外层的except块中");
}

 

puts("world");
}

 

上面的程序运行结果如下:
hello
存储保护异常
内层的except块中
world
Press any key to continue

 

  呵呵!感觉不错,大家可以在上面的程序基础之上改动一下,让它抛出一个被0除异常,看程序的运行结果是不是如预期那样。

  最后还有一点需要阐述,在C++的异常处理模型中,有一个throw关键字,也即在受监控的代码中抛出一个异常,那么在SEH异常处理模型中,是不是也应该有这样一个类似的关键字或函数呢?是的,没错!SEH异常处理模型中,对异常划分为两大类,第一种就是上面一些例程中所见到的,这类异常是系统异常,也被称为硬件异常;还有一类,就是程序中自己抛出异常,被称为软件异常。怎么抛出呢?还是Windows提供了的API函数,它的声明如下:

 

VOID RaiseException(
DWORD dwExceptionCode, // exception code
DWORD dwExceptionFlags, // continuable exception flag
DWORD nNumberOfArguments, // number of arguments in array
CONST DWORD *lpArguments // address of array of arguments
);

 

  很简单吧!实际上,在C++的异常处理模型中的throw关键字,最终也是对RaiseException()函数的调用,也即是说,throw是RaiseException的上层封装的更高级一类的函数,这以后再详细分析它的代码实现。这里还是看一个简单例子吧!代码如下:

 

#include 
#include

 

int seh_filer(int code)
{
switch(code)
{
case EXCEPTION_ACCESS_VIOLATION :
printf("存储保护异常,错误代码:%x\n", code);
break;
case EXCEPTION_DATATYPE_MISALIGNMENT :
printf("数据类型未对齐异常,错误代码:%x\n", code);
break;
case EXCEPTION_BREAKPOINT :
printf("中断异常,错误代码:%x\n", code);
break;
case EXCEPTION_SINGLE_STEP :
printf("单步中断异常,错误代码:%x\n", code);
break;
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED :
printf("数组越界异常,错误代码:%x\n", code);
break;
case EXCEPTION_FLT_DENORMAL_OPERAND :
case EXCEPTION_FLT_DIVIDE_BY_ZERO :
case EXCEPTION_FLT_INEXACT_RESULT :
case EXCEPTION_FLT_INVALID_OPERATION :
case EXCEPTION_FLT_OVERFLOW :
case EXCEPTION_FLT_STACK_CHECK :
case EXCEPTION_FLT_UNDERFLOW :
printf("浮点数计算异常,错误代码:%x\n", code);
break;
case EXCEPTION_INT_DIVIDE_BY_ZERO :
printf("被0除异常,错误代码:%x\n", code);
break;
case EXCEPTION_INT_OVERFLOW :
printf("数据溢出异常,错误代码:%x\n", code);
break;
case EXCEPTION_IN_PAGE_ERROR :
printf("页错误异常,错误代码:%x\n", code);
break;
case EXCEPTION_ILLEGAL_INSTRUCTION :
printf("非法指令异常,错误代码:%x\n", code);
break;
case EXCEPTION_STACK_OVERFLOW :
printf("堆栈溢出异常,错误代码:%x\n", code);
break;
case EXCEPTION_INVALID_HANDLE :
printf("无效句病异常,错误代码:%x\n", code);
break;
default :
if(code & (1<<29))
printf("用户自定义的软件异常,错误代码:%x\n", code);
else
printf("其它异常,错误代码:%x\n", code);
break;
}

return 1;
}

 

void main()
{
puts("hello");
__try
{
puts("try块中");

 

// 注意,主动抛出一个软异常
RaiseException(0xE0000001, 0, 0, 0);
}
__except(seh_filer(GetExceptionCode()))
{
puts("except块中");
}

 

puts("world");
}

 

上面的程序运行结果如下:
hello
try块中
用户自定义的软件异常,错误代码:e0000001
except块中
world
Press any key to continue

 

上面的程序很简单,这里不做进一步的分析。我们需要重点讨论的是,在__except模块中如何识别不同的异常,以便对异常进行很好的分类处理。毫无疑问,它当然是通过GetExceptionCode()或GetExceptionInformation ()函数来获取当前的异常错误代码,实际也即是DwExceptionCode字段。异常错误代码在winError.h文件中定义,它遵循Windows系统下统一的错误代码的规则。每个DWORD被划分几个字段,如下表所示:

例如我们可以在winbase.h文件中找到EXCEPTION_ACCESS_VIOLATION的值为0 xC0000005,将这个异常代码值拆开,来分析看看它的各个bit位字段的涵义。
C 0 0 0 0 0 0 5 (十六进制)
1100 0000 0000 0000 0000 0000 0000 0101 (二进制)
第3 0位和第3 1位都是1,表示该异常是一个严重的错误,线程可能不能够继续往下运行,必须要及时处理恢复这个异常。第2 9位是0,表示系统中已经定义了异常代码。第2 8位是0,留待后用。第1 6 位至2 7位是0,表示是FACILITY_NULL设备类型,它代表存取异常可发生在系统中任何地方,不是使用特定设备才发生的异常。第0位到第1 5位的值为5,表示异常错误的代码。

  如果程序员在程序代码中,计划抛出一些自定义类型的异常,必须要规划设计好自己的异常类型的划分,按照上面的规则来填充异常代码的各个字段值,如上面示例程序中抛出一个异常代码为0xE0000001软件异常。

总结

  (1) C++异常模型用try-catch语法定义,而SEH异常模型则用try-except语法;

  (2) 与C++异常模型相似,try-except也支持多层的try-except嵌套。

  (3) 与C++异常模型不同的是,try-except模型中,一个try块只能是有一个except块;而C++异常模型中,一个try块可以有多个catch块。

  (4) 与C++异常模型相似,try-except模型中,查找搜索异常模块的规则也是逐级向上进行的。但是稍有区别的是,C++异常模型是按照异常对象的类型来进行匹配查找的;而try-except模型则不同,它通过一个表达式的值来进行判断。如果表达式的值为1(EXCEPTION_EXECUTE_HANDLER),表示找到了异常处理模块;如果值为0(EXCEPTION_CONTINUE_SEARCH),表示继续向上一层的try-except域中继续查找其它可能匹配的异常处理模块;如果值为-1(EXCEPTION_CONTINUE_EXECUTION),表示忽略这个异常,注意这个值一般很少用,因为它很容易导致程序难以预测的结果,例如,死循环,甚至导致程序的崩溃等。

   (5) __except关键字后面跟的表达式,它可以是各种类型的表达式,例如,它可以是一个函数调用,或是一个条件表达式,或是一个逗号表达式,或干脆就是一个整型常量等等。最常用的是一个函数表达式,并且通过利用GetExceptionCode()或GetExceptionInformation ()函数来获取当前的异常错误信息,便于程序员有效控制异常错误的分类处理。

   (6) SEH异常处理模型中,异常被划分为两大类:系统异常和软件异常。其中软件异常通过RaiseException()函数抛出。RaiseException()函数的作用类似于C++异常模型中的throw语句。

<think>我们之前已经详细讨论过tqdm_asyncio的基本用法和高级功能,现在针对“如何使用”这个具体问题,我将提供一个更聚焦的指南,包含从基础到进阶的完整示例。 ###基础使用指南####1.单个异步任务进度条```pythonimportasynciofrom tqdm.asyncio importtqdm_asyncioasyncdef simple_task(total_steps=100):"""模拟一个长时间运行的异步任务"""pbar= tqdm_asyncio(total=total_steps, desc="处理中")fori inrange(total_steps):await asyncio.sleep(0.02)#模拟任务工作pbar.update(1)pbar.close()return"完成" #运行示例asyncio.run(simple_task()) ```####2.多任务并发监控```pythonasync defworker(task_id,steps):"""带进度的异步工作单元"""withtqdm_asyncio(total=steps,desc=f"任务{task_id}") aspbar:for _in range(steps):await asyncio.sleep(0.01 *(task_id+1))pbar.update(1)returnf"任务{task_id}完成" asyncdef concurrent_tasks():"""并发执行多个任务"""tasks= [worker(i,50 +i *10) fori inrange(5)]results= awaittqdm_asyncio.gather(*tasks)print("\n所有任务结果:")for resin results:print(f" -{res}")asyncio.run(concurrent_tasks())``` ###进阶使用技巧####1.动态描述更新```pythonasyncdef dynamic_description():total =200pbar= tqdm_asyncio(total=total, desc="初始状态")foriin range(total):awaitasyncio.sleep(0.01)pbar.update(1)ifi %40==0:pbar.set_description(f"阶段{i//40}")pbar.set_description("最终处理")pbar.close() asyncio.run(dynamic_description()) ```####2.自定义进度条样式```pythonasyncdef styled_progress():#创建自定义样式的进度条bar_format= "{l_bar}{bar:20}{r_bar}|进度:{percentage:.0f}%"pbar =tqdm_asyncio(total=150,desc="🍕处理披萨订单",bar_format=bar_format,colour="GREEN",ascii=">=")for iin range(150):await asyncio.sleep(0.01)pbar.update(1)ifi %30==0:pbar.set_postfix({"状态": f"阶段{i//30}"})pbar.close()asyncio.run(styled_progress()) ```###特殊场景处理####1.未知总量的进度条```pythonasyncdef unknown_total():"""当总大小未知时的进度处理"""pbar= tqdm_asyncio(desc="探索中", unit="步骤")fori inrange(1,101):await asyncio.sleep(0.03)pbar.update(1)ifi %20==0:pbar.total= None#进入未知模式pbar.refresh()pbar.total =100#完成后恢复总量pbar.close()asyncio.run(unknown_total())``` ####2.错误处理与进度回退```pythonasyncdef error_handling():total =100try:pbar =tqdm_asyncio(total=total,desc="危险操作")fori inrange(total):await asyncio.sleep(0.02)if i==60:raiseValueError("模拟错误")pbar.update(1)exceptException ase:pbar.set_postfix_str(f"❌错误:{str(e)}")pbar.refresh()await asyncio.sleep(1)#显示错误信息finally:pbar.close() asyncio.run(error_handling())``` ###性能优化实践####1.批量更新减少开销```pythonasync defbatch_updates():total=5000pbar =tqdm_asyncio(total=total,mininterval=0.5)#降低刷新频率batch_size =50processed=0whileprocessed <total:awaitasyncio.sleep(0.001*batch_size)#模拟批量处理update_amount =min(batch_size, total- processed)pbar.update(update_amount)processed +=update_amountpbar.close()asyncio.run(batch_updates()) ```####2.多进度条并行控制```pythonasync defparallel_bars():"""管理多个独立进度条"""async defsub_task(desc,steps):with tqdm_asyncio(total=steps, desc=desc, position=None) aspbar:for_in range(steps):await asyncio.sleep(0.02)pbar.update(1)#同时运行三个独立任务awaitasyncio.gather(sub_task("下载",80),sub_task("处理",120),sub_task("上传",60)) asyncio.run(parallel_bars())``` ###最佳实践总结1. **资源管理**:-始终使用`with`语句或手动`close()`确保释放资源-避免在事件循环中创建过多实例2. **用户体验**:-使用`desc`提供清晰的任务描述-利用`set_postfix`显示动态信息-为长时间任务设置`miniters`减少刷新3.**错误处理**:-在`try/finally`中确保进度条关闭-使用`set_postfix_str`显示错误状态4.**性能优化**:-批量更新减少刷新频率-调整`mininterval`平衡实时性和开销§§相关问题§§: 1.如何在tqdm_asyncio中实现嵌套进度条(主进度条+子任务进度条)? 2.使用tqdm_asyncio时如何避免多个进度条重叠显示?3.如何为tqdm_asyncio进度条添加自定义颜色和样式? 4.在异步任务中如何动态调整tqdm_asyncio进度条的总量?</think>### 全面指南:tqdm_asyncio 的使用方法与最佳实践 `tqdm_asyncio` 是 `tqdm` 库的异步版本,专为 Python 的异步编程模型设计。它允许你在异步任务中创建实时更新的进度条,是处理异步 I/O 操作的理想工具。 #### 基础使用方法 **1. 基本进度条实现** ```python import asyncio from tqdm.asyncio import tqdm_asyncio async def async_task(total_steps=100): """基本异步任务进度条""" pbar = tqdm_asyncio(total=total_steps, desc="处理中") for i in range(total_steps): await asyncio.sleep(0.05) # 模拟异步操作 pbar.update(1) pbar.close() return "任务完成" # 运行任务 asyncio.run(async_task()) ``` **2. 并发任务监控** ```python async def worker(task_id, steps): """带进度的异步工作单元""" with tqdm_asyncio(total=steps, desc=f"任务{task_id}") as pbar: for _ in range(steps): await asyncio.sleep(0.01 * (task_id + 1)) pbar.update(1) return f"任务{task_id}完成" async def main_concurrent(): """并发执行多个任务""" tasks = [worker(i, 50 + i * 10) for i in range(5)] results = await tqdm_asyncio.gather(*tasks) print("\n所有任务结果:") for res in results: print(f" - {res}") # 运行并发任务 asyncio.run(main_concurrent()) ``` #### 高级功能应用 **1. 嵌套进度条(主进度+子进度)** ```python async def process_item(item_id): """处理单个项目的子进度条""" with tqdm_asyncio(total=100, desc=f"项目{item_id}", leave=False) as sub_pbar: for step in range(10): await asyncio.sleep(0.05) sub_pbar.update(10) return f"项目{item_id}处理完成" async def batch_processing(): """批量处理的主进度条""" items = list(range(1, 6)) main_pbar = tqdm_asyncio(items, desc="批量处理") results = [] async for item in main_pbar: result = await process_item(item) results.append(result) main_pbar.set_postfix_str(f"当前: {item}") print("\n处理结果:") for res in results: print(f"✓ {res}") asyncio.run(batch_processing()) ``` **2. 实时速度监控** ```python async def download_simulator(): """模拟带速度显示的下载进度""" total_size = 1024 * 1024 # 1MB chunk_size = 8192 # 启用速率显示 with tqdm_asyncio( total=total_size, unit='B', unit_scale=True, unit_divisor=1024, miniters=1, desc="下载模拟" ) as pbar: downloaded = 0 while downloaded < total_size: chunk = min(chunk_size, total_size - downloaded) await asyncio.sleep(0.001) # 模拟下载延迟 downloaded += chunk pbar.update(chunk) asyncio.run(download_simulator()) ``` #### 自定义配置选项 **1. 自定义进度条样式** ```python async def custom_style_demo(): """自定义进度条外观""" bar_format = "{l_bar}{bar:30}{r_bar}| 进度: {percentage:.0f}%" pbar = tqdm_asyncio( total=200, desc="🎨 自定义样式", bar_format=bar_format, colour="MAGENTA", ascii="▏▎▍▌▋▊▉" ) for i in range(200): await asyncio.sleep(0.01) pbar.update(1) if i % 40 == 0: pbar.set_postfix({"阶段": f"{i//40 + 1}/5"}) pbar.close() asyncio.run(custom_style_demo()) ``` **2. 动态调整进度条参数** ```python async def dynamic_progress(): """运行时动态调整进度条""" pbar = tqdm_asyncio(total=100, desc="初始状态") # 第一阶段 for i in range(30): await asyncio.sleep(0.03) pbar.update(1) # 动态修改参数 pbar.set_description("第二阶段") pbar.total = 150 # 扩展总进度 pbar.refresh() # 刷新显示 # 第二阶段 for i in range(120): await asyncio.sleep(0.02) pbar.update(1) pbar.close() asyncio.run(dynamic_progress()) ``` #### 错误处理与恢复 **1. 异常处理与进度条清理** ```python async def error_handling_demo(): """带错误处理的进度条""" try: pbar = tqdm_asyncio(total=100, desc="危险操作") for i in range(100): await asyncio.sleep(0.02) if i == 60: raise ValueError("模拟错误") pbar.update(1) except Exception as e: pbar.set_postfix_str(f"❌ 错误: {str(e)}") pbar.refresh() await asyncio.sleep(1) # 显示错误信息 finally: pbar.close() asyncio.run(error_handling_demo()) ``` **2. 断点续传进度** ```python async def resume_progress(): """从断点恢复进度""" try: # 尝试加载保存的进度 with open("progress.txt", "r") as f: start_point = int(f.read()) except: start_point = 0 total = 200 pbar = tqdm_asyncio(total=total, initial=start_point, desc="断点续传") for i in range(start_point, total): await asyncio.sleep(0.03) pbar.update(1) # 定期保存进度 if i % 10 == 0: with open("progress.txt", "w") as f: f.write(str(i)) pbar.close() # 清理进度文件 import os os.remove("progress.txt") asyncio.run(resume_progress()) ``` ### 最佳实践指南 1. **资源管理**: - 始终使用 `with` 语句或手动调用 `close()` 确保资源释放 ```python # 推荐方式 with tqdm_asyncio(...) as pbar: # 任务代码 # 替代方式 pbar = tqdm_asyncio(...) try: # 任务代码 finally: pbar.close() ``` 2. **性能优化**: - 对高频更新任务设置 `mininterval` 参数 ```python tqdm_asyncio(mininterval=0.2) # 每秒最多更新5次 ``` 3. **多环境适配**: - 在Jupyter中使用 `tqdm.notebook.tqdm` 替代 ```python # Jupyter专用 from tqdm.notebook import tqdm as tqdm_notebook ``` 4. **用户体验增强**: - 使用 `set_description()` 提供任务状态 - 使用 `set_postfix()` 显示额外信息 ```python pbar.set_postfix({"状态": "处理中", "速度": f"{speed} items/s"}) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值