支持超时机制的函数调用

我们的程序在进行一些复杂操作的过程可能会耗费较长的时间, 甚至有时候还会阻止程序的继续执行. 为了保证我们的程序得以继续运行, 我们可以为函数调用增加超时机制.

对于一个没有返回值的函数, 我们可以直接使用Thread来支持超时:

/// <summary>
/// 在要求时间内运行指定Action
/// </summary>
/// <param name="action">目标Action</param>
/// <param name="timeout">超时时间</param>
void RunActionWithinTime(Action action, TimeSpan timeout)
{
	Thread thread = new Thread(new ThreadStart(action));
	thread.Start();
	thread.Join(timeout); // 等待线程执行
	thread.Abort(); // 终止线程
	thread.Join(); // 保证线程终止
}
执行一个"耗时较长"的操作:
RunActionWithinTime(() => { Thread.Sleep(TimeSpan.FromSeconds(10)); },
	TimeSpan.FromSeconds(1)); // 1秒后会继续往下执行
使用以上方法我们已经为程序增加了超时终止的支持, 但同时带来的新问题是我们无法调用一个带返回值的函数并方便的得到其返回值了.
因为最终的目标函数带返回值, 但该调用方式无法支持返回值,  本人首先考虑到的是将返回值通过out参数方法返回:

void GetReturnValueViaOutParam<TResult>
	(Func<TResult> func, out TResult result)
{
	result = func.Invoke(); // (1)
}
使用方式:

string AlwaysTimeout()
{
	while (true) Thread.Sleep(TimeSpan.FromSeconds(1));
	return "impossible result";
}

string result = "unknown"; // (2)
RunActionWithinTime(
	() => GetReturnValueViaOutParam(AlwaysTimeout, out result),
	TimeSpan.FromSeconds(1));
Console.WriteLine(result); // 输出值为"unknown" (3)

如此一来便可以获取目标函数的返回值, 但该方式看起来相当丑陋, 所以接着考虑如何改进.
观察上面的代码, 可以看到我们首先定义了一个变量用于保存结果, 接着在GetReturnValueViaOutParam函数中调用目标函数并为结果赋值, 最终使用结果.
照着这个流程最终有了以下实现:

/// <summary>
/// 在要求时间内运行指定函数
/// </summary>
/// <typeparam name="TResult">函数返回值类型</typeparam>
/// <param name="func">目标函数</param>
/// <param name="timeout">超时时间</param>
/// <param name="returnOnTimeout">超时时的返回值</param>
/// <returns>函数调用结果</returns>
TResult RunFuncWithinTime<TResult>(Func<TResult> func,
	TimeSpan timeout, TResult returnOnTimeout = default(TResult))
{
	TResult result = returnOnTimeout; // (2)
	Action actionMayTimeout = () => result = func.Invoke(); // (1)
	RunActionWithinTime(actionMayTimeout, timeout);
	return result; // (3)
}
使用方式如下:

var result = RunFuncWithinTime(AlwaysTimeout, TimeSpan.FromSeconds(1), "timeout");
Console.WriteLine(result); // "timeout"
不难看出该辅助函数实际仅仅是对上面流程的进一步封装, 但在使用上已相当接近于直接调用目标函数的方法了.
虽然RunFuncWithinTime函数目前仅支持无参函数的调用, 但参考Action或者Func的实现, 我们可以通过添加多个重载方式来满足日常需求.
这里仅给出4个参数的函数调用重载, 参照该形式, 其他个数参数的重载也不难实现.

/// <summary>
/// 4个参数的重载形式
/// </summary>
/// ...
TResult RunFuncWithinTime<T1, T2, T3, T4, TResult>
	(Func<T1, T2, T3, T4, TResult> func, T1 param1, T2 param2, T3 param3, T4 param4,
	TimeSpan timeout, TResult returnOnTimeout = default(TResult))
{
	TResult result = returnOnTimeout;

	Action actionMayTimeout = () => result = func.Invoke(param1, param2, param3, param4);
	RunActionWithinTime(actionMayTimeout, timeout);

	return result;
}

本文仅为笔者在维护项目的过程中临时考虑到方法的总结, 难免存在不足的地方, 如有建议欢迎提出.

### 三级标题:C语言中实现函数调用的超时返回机制 在 C 语言中,实现函数调用的超时返回机制通常依赖于操作系统提供的系统调用或库函数。具体实现方式取决于函数的类型及其是否支持非阻塞操作。以下是一些常见的实现方法: #### 使用 `select` 实现 I/O 操作的超时控制 `select` 是一种用于多路复用 I/O 操作的系统调用,它允许程序监视多个文件描述符,以判断它们是否准备好进行读、写或异常事件处理。通过设置 `struct timeval` 结构体,可以指定 `select` 的最大等待时间。如果在指定时间内没有文件描述符就绪,`select` 会返回 0,表示超时。 ```c struct timeval timeout = {3, 0}; // 设置超时为3秒 int n = select(maxfd + 1, &readfds, NULL, NULL, &timeout); if (n == 0) { printf("select timeout\n"); // 超时无事件 } ``` 此方法适用于文件描述符的读取或写入操作,并且在 Linux 系统上广泛使用[^1]。 #### 使用信号机制实现函数调用超时 在某些情况下,函数调用本身并不支持超时机制,此时可以使用信号机制来实现超时控制。例如,可以通过 `alarm` 函数设置定时器,在指定时间后发送 `SIGALRM` 信号,从而中断当前的函数调用。 ```c #include <signal.h> #include <unistd.h> void handle_timeout(int sig) { // 处理超时逻辑 } int main() { signal(SIGALRM, handle_timeout); alarm(5); // 设置5秒后触发信号 // 执行可能阻塞的函数调用 // ... alarm(0); // 取消定时器 return 0; } ``` 需要注意的是,信号处理函数必须是异步信号安全的,并且不能在信号处理函数中执行复杂的操作[^4]。 #### 使用多线程实现函数调用超时 另一种实现函数调用超时的方法是使用多线程。主线程启动一个子线程来执行目标函数,并在指定时间后检查子线程是否完成。如果子线程未在规定时间内完成,则可以采取相应的超时处理措施。 ```c #include <pthread.h> #include <unistd.h> void* thread_function(void* arg) { // 执行耗时操作 sleep(10); return NULL; } int main() { pthread_t thread; pthread_create(&thread, NULL, thread_function, NULL); // 等待5秒 sleep(5); // 检查子线程是否完成 if (pthread_kill(thread, 0) == 0) { // 子线程仍在运行,超时处理 pthread_cancel(thread); } pthread_join(thread, NULL); return 0; } ``` 此方法适用于不支持超时机制函数调用,并且可以避免阻塞主线程。 #### 使用 `setjmp` 和 `longjmp` 实现函数调用跳转 在某些特殊情况下,可以使用 `setjmp` 和 `longjmp` 来实现函数调用的跳转。当函数调用超时时,可以跳转到指定的代码位置,从而结束当前的操作。 ```c #include <setjmp.h> #include <signal.h> jmp_buf env; void handle_timeout(int sig) { longjmp(env, 1); } int main() { signal(SIGALRM, handle_timeout); if (setjmp(env) == 0) { alarm(5); // 设置5秒后触发信号 // 执行可能阻塞的函数调用 // ... alarm(0); // 取消定时器 } else { // 超时处理 } return 0; } ``` 需要注意的是,`setjmp` 和 `longjmp` 的使用可能导致程序状态的不一致性,因此应谨慎使用。 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值