我有一个对象obj
,它是第三方组件。
// this could take more than 30 seconds
int result = obj.PerformInitTransaction();
我不知道里面发生了什么。 我知道的是如果它花费更长的时间,它就会失败。
如何设置一个超时机制来控制这个操作,如果它花费的时间超过30秒,我只需抛出MoreThan30SecondsException
异常?
我有一个对象obj
,它是第三方组件。
// this could take more than 30 seconds
int result = obj.PerformInitTransaction();
我不知道里面发生了什么。 我知道的是如果它花费更长的时间,它就会失败。
如何设置一个超时机制来控制这个操作,如果它花费的时间超过30秒,我只需抛出MoreThan30SecondsException
异常?
using System.Threading;
class Program {
static void DoSomething() {
try {
// your call here...
obj.PerformInitTransaction();
} catch (ThreadAbortException) {
// cleanup code, if needed...
}
}
public static void Main(params string[] args) {
Thread t = new Thread(DoSomething);
t.Start();
if (!t.Join(TimeSpan.FromSeconds(30))) {
t.Abort();
throw new Exception("More than 30 secs.");
}
}
}
更简单的方法是使用Task.Wait(TimeSpan)
:
using System.Threading.Tasks;
var task = Task.Run(() => obj.PerformInitTransaction());
if (task.Wait(TimeSpan.FromSeconds(30)))
return task.Result;
else
throw new Exception("Timed out");
如果您不想阻塞主线程,可以使用System.Threading.Timer:
private Thread _thread;
void Main(string[] args)
{
_thread = new ThreadStart(ThreadEntry);
_thread.Start();
Timer timer = new Timer(Timeout,null,30000,Timeout.Infinite);
}
void ThreadEntry()
{
int result = obj.PerformInitTransaction();
}
void TimeOut(object state)
{
// Abort the thread - see the comments
_thread.Abort();
throw new ItTimedOutException();
}
Jon Skeet提供了一种比强制终止线程更温和的方法 (优雅地停止工作线程)。
但是,由于你无法控制PerformInitTransaction()
正在执行的操作,因此当Abort失败并使对象处于无效状态时,你很难采取任何行动。如前所述,如果你能够清理aborting PerformInitTransaction
留下的任何问题,你可以通过捕获ThreadAbortException
这一异常来实现,但由于这是第三方调用,这意味着你必须猜测你已经离开他们的方法处于什么状态。
PerformInitTransaction
应该真正提供超时。
public static bool DoWithTimeout(Action action, int timeout)
{
Exception ex = null;
CancellationTokenSource cts = new CancellationTokenSource();
Task task = Task.Run(() =>
{
try
{
using (cts.Token.Register(Thread.CurrentThread.Abort))
{
action();
}
}
catch (Exception e)
{
if (!(e is ThreadAbortException))
ex = e;
}
}, cts.Token);
bool done = task.Wait(timeout);
if (ex != null)
throw ex;
if (!done)
cts.Cancel();
return done;
}
对于有返回值的函数:
public static bool DoWithTimeout<T>(Func<T> func, int timeout, out T result)
{
Exception ex = null;
result = default(T);
T res = default(T);
CancellationTokenSource cts = new CancellationTokenSource();
Task task = Task.Run(() =>
{
try
{
using (cts.Token.Register(Thread.CurrentThread.Abort))
{
res = func();
}
}
catch (Exception e)
{
if (!(e is ThreadAbortException))
ex = e;
}
}, cts.Token);
bool done = task.Wait(timeout);
if (ex != null)
throw ex;
if (done)
result = res;
else
cts.Cancel();
return done;
}
using System.Threading.Tasks;
var timeToWait = 30000; //ms
Task.Run(async () =>
{
await Task.Delay(timeToWait);
//do your timed task i.e. --
int result = obj.PerformInitTransaction();
});
在像这样的操作中,你需要小心终止它,尤其是在一个第三方组件中,你(可能)没有访问修改代码的权限。
如果你终止了操作,那么你就不知道你留下了基础类的什么状态。例如,它可能已经获取了一个锁,而你的终止导致该锁未被释放。即使你在终止操作后销毁对象,它可能已经改变了一些对它全局的状态,因此你将无法可靠地创建一个新实例而不需要重新启动。
你可以考虑在一个线程中调用该方法,在超时后终止线程并引发异常。此外,你还需要处理 ThreadBorted 异常。
.NET 6 / C# 10 的新方法:
var task = Task.Run(() => SomeMethod(input));
return await task.WaitAsync(TimeSpan.FromSeconds(30));
我刚在一个.NET 4.0应用程序中遇到了这个问题(没有访问Task.Run
、Task.Delay
等)。如果您原谅最后一行(即setTimeout部分),它相当简洁。
int sleepTime = 10000;
Action myAction = () => {
// my awesome cross-thread update code
this.BackColor = Color.Red;
};
new System.Threading.Thread(() => { System.Threading.Thread.Sleep(sleepTime); if (InvokeRequired) myAction(); else myAction(); }).Start();