支持超时机制的函数调用

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

对于一个没有返回值的函数, 我们可以直接使用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;
}

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值