【问题标题】: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

【问题讨论】:

    标签: c# multithreading timeout


    【解决方案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 确实应该是提供超时的那个。

    【讨论】:

    【解决方案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();
                

                【讨论】:

                  猜你喜欢
                  • 2011-09-05
                  • 2018-07-20
                  • 1970-01-01
                  • 2011-06-25
                  • 2019-02-12
                  • 2018-01-27
                  • 2013-03-28
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多