为操作设置超时

Posted

技术标签:

【中文标题】为操作设置超时【英文标题】:Set timeout to an operation 【发布时间】:2011-01-16 22:46:49 【问题描述】:

我有对象obj,它是第 3 方组件,

// this could take more than 30 seconds
int result = obj.PerformInitTransaction(); 

我不知道里面发生了什么。 我知道如果需要更长的时间,它就会失败。

如何为此操作设置超时机制,这样如果超过 30 秒我就抛出 MoreThan30SecondsException

【问题讨论】:

【参考方案1】:

您可以在单独的线程中运行该操作,然后在线程连接操作上设置超时:

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.");
        
    

【讨论】:

@Bomboca:我回滚了你的编辑,我抛出的 Exception 不应该是 ThreadAbortException,这是 CLR 在调用 Abort 时抛出的东西。 这是一个阻塞调用,如果你此时需要主线程做其他事情,这是行不通的!【参考方案2】:

更简单地使用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");

【讨论】:

这是非常简单的,从 .net 4.0 开始就可以使用了。 @anwar 我会再次遇到这个答案,我正在使用 .NET Framework 4.0,所以请让我纠正你:Task.Run 从 .NET 4.5 开始可用。 link 这不会结束线程。建议与 CancellationToken 一起使用。【参考方案3】:

如果你不想阻塞主线程,你可以使用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 有一种比 abort 更不有力的方式来停止线程 (Shutting Down Worker Threads Gracefully)。

但是,由于您无法控制 PerformInitTransaction() 正在执行的操作,因此当 Abort 失败并使对象处于无效状态时,您无能为力。如前所述,如果您能够清理中止 PerformInitTransaction 挂起的任何内容,您可以通过捕获 ThreadAbortException 来执行此操作,但由于它是第 3 方调用,这意味着猜测您已经离开他们的方法的状态在。

PerformInitTransaction 确实应该是提供超时的那个。

【讨论】:

文章“优雅地关闭工作线程”的链接已损坏。我可以建议jonskeet.uk/csharp/threads/shutdown.html【参考方案4】:

以下是两个实现,它们也会抛出内部任务中发生的任何异常。

对于动作(无返回值):

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;

对于 Funcs(带返回值):

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;

【讨论】:

【参考方案5】:

我认为这是最简单的:

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(); 
);

【讨论】:

【参考方案6】:

您需要小心中止这样的操作,尤其是当它位于您(可能)无法访问要修改的代码的第 3 方组件中时。

如果您中止操作,那么您将不知道您将底层类置于什么状态。例如,它可能已经获得了一个锁,而您的 about 导致该锁没有被释放。即使您在中止操作后销毁该对象,它也可能改变了它的某些全局状态,因此您将无法在不重新启动的情况下可靠地创建新实例。

【讨论】:

【参考方案7】:

您可能会考虑在线程中调用方法,并在超时时中止线程并引发异常。此外,在这种情况下,您必须处理 ThreadBorted 异常。

【讨论】:

【参考方案8】:

有一个很好的通用解决方案示例,它使用帮助器类here。

它使用 Action 委托来避免前面示例中显示的线程创建/销毁。

我希望这会有所帮助。

【讨论】:

【参考方案9】:

这是我会使用的。工作方式类似于 javascript 超时的工作方式。

public class Toolz 
    public static System.Threading.Tasks.Task<object> SetTimeout(Func<object> func, int secs) 
        System.Threading.Thread.Sleep(TimeSpan.FromSeconds(secs));
        return System.Threading.Tasks.Task.Run(() => func());
    


class Program 
    static void Main(string[] args) 
        Console.WriteLine(DateTime.Now);
        Toolz.SetTimeout(() => 
            Console.WriteLine(DateTime.Now);
            return "";
        , 10);
    


【讨论】:

【参考方案10】:

我刚刚在 .NET 4.0 应用程序中遇到了这个问题(无法访问 Task.RunTask.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();

【讨论】:

以上是关于为操作设置超时的主要内容,如果未能解决你的问题,请参考以下文章

如何设置socket的Connect超时

redis基本操作,基于StringRedisTemplate,存储,取值,设置超时时间,获取超时时间,插入list操作

操作成功完成后是否应取消Twisted超时?

linux 系统默认的无操作超时时间怎么设置

Unix网络编程 高级IO套接字设置超时

Ansible 的委托 并发和任务超时