【问题标题】:Creating Tasks with timeouts创建具有超时的任务
【发布时间】:2014-01-10 03:06:59
【问题描述】:

我希望执行一系列任务,每个任务都有自己的超时时间。

我从这里借用了创建超时任务的扩展方法 http://blogs.msdn.com/b/pfxteam/archive/2011/11/10/10235834.aspx

所以代码在下面

 public static Task TimeoutAfter(this Task task, int millisecondsTimeout)
        {
            // Short-circuit #1: infinite timeout or task already completed
            if (task.IsCompleted || (millisecondsTimeout == Timeout.Infinite))
            {
                // Either the task has already completed or timeout will never occur.
                // No proxy necessary.
                return task;
            }

            // tcs.Task will be returned as a proxy to the caller
            TaskCompletionSource<VoidTypeStruct> tcs = new TaskCompletionSource<VoidTypeStruct>();

            // Short-circuit #2: zero timeout
            if (millisecondsTimeout == 0)
            {
                // We've already timed out.
                tcs.SetException(new TimeoutException());
                return tcs.Task;
            }

            // Set up a timer to complete after the specified timeout period
            Timer timer = new Timer(state =>
            {
                // Recover your state information
                var myTcs = (TaskCompletionSource<VoidTypeStruct>)state;
                // Fault our proxy with a TimeoutException
                myTcs.TrySetException(new TimeoutException());
            }, tcs, millisecondsTimeout, Timeout.Infinite);

            // Wire up the logic for what happens when source task completes
            task.ContinueWith(antecedent =>
                                {
                                    timer.Dispose(); // Cancel the timer
                                    MarshalTaskResults(antecedent, tcs); // Marshal results to proxy
                                },
                                CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);

            return tcs.Task;
        }

public class Program
    {
        private static List<int> Output = new List<int>();

        private static Random _random = new Random();
        public static void LongRunningTask(string message)
        {
            Console.WriteLine(message);
            Console.WriteLine("Managed thread Id " + Thread.CurrentThread.ManagedThreadId);            
            //Simulate a long running task
            Thread.Sleep(TimeSpan.FromSeconds(3));
            var number = _random.Next();
            Console.WriteLine("Adding " + number);
            Output.Add(number);
        }
        public static void Main(string[] args)
        {
            var tasks = new List<Task>();

            var t1 = Task.Factory.StartNew(_ => LongRunningTask("Entering task1"),TaskCreationOptions.AttachedToParent).TimeoutAfter(10);
            var t2 = Task.Factory.StartNew(_ => LongRunningTask("Entering task2"),TaskCreationOptions.AttachedToParent);
            var t3 = Task.Factory.StartNew(_ => LongRunningTask("Entering task3"),TaskCreationOptions.AttachedToParent);

            tasks.Add(t1);
            tasks.Add(t2);
            tasks.Add(t3);

            try
            {
                Task.WaitAll(tasks.ToArray());
            }
            catch (Exception ex)
            {
                Console.WriteLine("There was an exception");
                Console.WriteLine(ex.InnerException.Message);
            }

            Console.WriteLine("Output :");
            Output.ForEach(_ => Console.WriteLine(_));

            Console.ReadLine();
        }
    }



the output 

    Entering task1
    Managed thread Id 10
    Entering task2
    Managed thread Id 11
    Entering task3
    Managed thread Id 14
    Adding 453738994
    Adding 156432981
    Adding 1340619865
    There was an exception
    The operation has timed out.
    Output :
    453738994
    156432981
    1340619865

现在,我无法理解的是为什么即使我指定了超时并且发生了超时异常,t1 仍然完成。

我正在使用 .net 4。

编辑:

确保超时任务在超时期限后不做任何事情,即完全取消任务。

public class Program
    {
        private static List<int> Output = new List<int>();

        private static Random _random = new Random();
        public static int LongRunningTask(string message)
        {
            Console.WriteLine(message);
            Console.WriteLine("Managed thread Id " + Thread.CurrentThread.ManagedThreadId);            
            //Simulate a long running task
            Thread.Sleep(TimeSpan.FromSeconds(2));
            var number = _random.Next();
            Console.WriteLine("Adding " + number + " From thread  - " + Thread.CurrentThread.ManagedThreadId);
            return number;
        }
        public static void Main(string[] args)
        {
            Console.WriteLine("In Main");
            Console.WriteLine("Managed thread Id " + Thread.CurrentThread.ManagedThreadId);
            var cts = new CancellationTokenSource();
            var tasks = new List<Task>();

            var t1 = Task.Factory.StartNew(_ => LongRunningTask("Entering task1"), TaskCreationOptions.AttachedToParent)
                                 .ContinueWith(_ => Output.Add(_.Result),cts.Token)
                                 .TimeoutAfter(1000);
            var t2 = Task.Factory.StartNew(_ => LongRunningTask("Entering task2"), TaskCreationOptions.AttachedToParent)
                                 .ContinueWith(_ => Output.Add(_.Result));
            var t3 = Task.Factory.StartNew(_ => LongRunningTask("Entering task3"), TaskCreationOptions.AttachedToParent)
                                 .ContinueWith(_ => Output.Add(_.Result));

            tasks.Add(t1);
            tasks.Add(t2);
            tasks.Add(t3);

            try
            {
                Task.WaitAll(tasks.ToArray());
            }
            catch (Exception ex)
            {
                Console.WriteLine("There was an exception");
                Console.WriteLine(ex.InnerException.Message);
                cts.Cancel();
            }

            Console.WriteLine("Output :");
            Output.ForEach(_ => Console.WriteLine(_));

            Console.ReadLine();
        }
    }

输出:

In Main
Managed thread Id 9
Entering task1
Managed thread Id 10
Entering task2
Managed thread Id 11
Entering task3
Managed thread Id 13
Adding 1141027730 From thread  - 10
Adding 1856518562 From thread  - 13
Adding 1856518562 From thread  - 11
There was an exception
The operation has timed out.
Output :
1141027730
1856518562
1856518562

【问题讨论】:

    标签: .net c#-4.0 asynchronous timeout task-parallel-library


    【解决方案1】:

    输出包含三个值,因为程序等待所有任务Task.WaitAll(tasks.ToArray()); 并且输出是公共字段(因为闭包)

    你可以只保留第一个任务,你会看到另一个结果

    Entering task1
    Managed thread Id 10
    There was an exception
    The operation has timed out.
    Output :
    Adding 1923041190
    Managed thread Id 10
    

    请注意,Adding 已拨打电话,但Output 中没有该号码。调用 Adding 是因为 LongRunningTask 在此任务中工作 Task.Factory.StartNew(_ =&gt; LongRunningTask("Entering task1"), TaskCreationOptions.AttachedToParent) 并且已在不同的线程上引发了异常。此异常不会影响LongRunningTask

    编辑:

    有几种选择:

    1. 调用t1.Wait异常会立即重新抛出,你可以取消任务
    2. 在 ContinueWith 之前致电 TimeoutAfter(10)

          var t1 = Task.Factory.StartNew(() => LongRunningTask("Entering task1"))
                               .TimeoutAfter(10)
                               .ContinueWith(_=> Output.Add(_.Result), cts.Token);
      

    Continue只有在完成TimeoutAfter和LongRunningTask后才会执行,但是你必须更新TimeoutAfter,你必须返回Task&lt;Result&gt;而不是Task

        public static Task<Result> TimeoutAfter<Result>(this Task<Result> task, int millisecondsTimeout)
        {
            // Short-circuit #1: infinite timeout or task already completed
            if (task.IsCompleted || (millisecondsTimeout == Timeout.Infinite))
            {
                Console.WriteLine("task.IsCompleted");
                // Either the task has already completed or timeout will never occur.
                // No proxy necessary.
                return task;
            }
            // tcs.Task will be returned as a proxy to the caller
            var tcs = new TaskCompletionSource<Result>();
    
            // Short-circuit #2: zero timeout
            if (millisecondsTimeout == 0)
            {
                //                Console.WriteLine("millisecondsTimeout == 0");
                // We've already timed out.
                tcs.SetException(new TimeoutException());
                return tcs.Task;
            }
    
            // Set up a timer to complete after the specified timeout period
            var timer = new Timer(state => tcs.TrySetException(new TimeoutException()), null, millisecondsTimeout, Timeout.Infinite);
    
            // Wire up the logic for what happens when source task completes
            task.ContinueWith(antecedent =>
                {
                    timer.Dispose();
                    MarshalTaskResults(antecedent, tcs); 
                }, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
    
            return tcs.Task;
        }
    

    【讨论】:

    • 如何告诉任务在发生超时后停止执行,以便在后台运行时不会向输出添加任何内容。向它提供取消令牌源,然后取消它不起作用。
    • 仅在代码中 Task.WaitAll 重新抛出 TimoutException,所有任务都已完成,cts.Cancel() 无关紧要。把cts.Cancel()放在Task.WaitAll前面,任务就会被取消
    • 无论超时如何都会取消任务。我想要的行为是在超时或异常的情况下取消任务。
    • 非常重要的是要注意 TimeoutAfter 不会以任何方式终止任务。 LongRunningTask 将始终执行完成,无论是否触发超时。请参阅此处的示例:dotnetfiddle.net/dny3c1
    【解决方案2】:

    TimeoutAfter() 方法对底层Task 没有任何作用。因此,即使发生超时,Task 仍会继续执行并最终完成。

    如果不修改LongRunningTask(),没有好的方法可以解决这个问题。如果你可以修改LongRunningTask(),那么你应该做的是让它接受CancellationToken并在适当的时候检查它。

    您的ContinueWith() 尝试没有改变任何东西,因为Task 仍然完成,所以继续触发。

    有什么帮助是这样的:

    var t1 = Task.Factory.StartNew(() => LongRunningTask("Entering task1"))
                         .TimeoutAfter(1000)
                         .ContinueWith(t => Output.Add(t.Result), cts.Token);
    

    如果你这样做,那么t1 将代表继续,因此如果发生超时,它将出错(等待它会引发异常)。如果您不希望这样,请在访问其Result 之前检查t 的状态。

    另外,你不应该像这样在List 上调用Add(),因为Add() 不是线程安全的,并且有可能多个线程会尝试同时添加到它。为避免这种情况,请使用并发集合之一或锁定。

    【讨论】:

      【解决方案3】:

      仅供参考, 我最终做了这样的事情

      var t1 = Task.Factory.StartNew(_ => LongRunningTask("Entering task1"),                              TaskCreationOptions.AttachedToParent)
                                           .TimeoutAfter(1000)
                                           .ContinueWith(_ =>
                                      {
                                          if(!(_.IsCanceled || _.IsFaulted))
                                              Output.Add(_.Result);
                                      }
                                      , cts.Token);
      

      【讨论】:

      • 顺便说一句,我认为只有在不使用该参数时才应该使用_。如果你确实使用它,它应该类似于antecedent、task 或至少t。
      【解决方案4】:

      尽管我找到了之前的答案,但我发现使用基于事件的框架很容易实现。

      让我解释一下这个要求,我必须将所有内容都封装在异步中,这可能需要超过 50 毫秒的时间,以便用户与屏幕的交互保持流畅。因此,我所有的套接字请求和对服务器的响应都需要被包装。这些类型的编程典型地涉及请求某些东西然后得到答案请求和答案不需要遵循FIFO,因为人们可以问,黄金的价格是多少,并让数据以每秒数百次的速度流动,然后问我的帐户是什么价值。

      这是我的实现,我添加了一些 cmets 让一些人更容易理解。

      internal Task<string[]> RequestAccountNamesAsync() => RequestAccountNamesAsync(-1);
      internal Task<string[]> RequestAccountNamesAsync(int millisecondsTimeout) => RequestAccountNamesAsync(CancellationToken.None, millisecondsTimeout);
      internal Task<string[]> RequestAccountNamesAsync(CancellationToken token,int millisecondsTimeout = 1000 )
      {
      
          var t1 =  Task.Factory.StartNew<string[]>( () =>
          {
              try
              {
                  //the result type of the Task
                  string[] result = null;
      
                  //local helper function used to hookup the event
                  void Method(object sender, OnAccountsReceivedArgs ac)
                  {
                      this.OnAccountsReceived -= Method;
                      result = ac.Accounts;
                  }
      
                  //event responsible for reacting on the "on complete event"
                  this.OnAccountsReceived += Method;
      
      
      
                  //item responsible for initiating the socket request
                  clientSocket.reqManagedAccts();
      
                  //measure time-out 
                  DateTimeOffset startTime = DateTimeOffset.Now;
      
                  //loop waiting for the result to come from the server
                  while (result == null)
                  {
                      if (millisecondsTimeout > 0 && DateTimeOffset.Now.Subtract(startTime).Milliseconds >= millisecondsTimeout)
                          throw new TimeoutException();
                      //if the value of the millisecondsTimeout argument is zero, the thread relinquishes the remainder of its 
                      // time slice to any thread of equal priority that is ready to run
                      // If there are no other threads of equal priority that are ready to run, execution of the current thread
                      // is not suspended. 
                      Thread.Sleep(0);
                  }
                  return result;
              }
              catch (Exception e)
              {                    
                  //integrate my proprietary logging framework
                  logger.Enqueue<IBClient>(e);
                  throw e;
              }
          });
      
          return t1;
      
      }
      

      【讨论】:

      • 我的方法是内部的,因为它们不能从应用程序外部调用。
      猜你喜欢
      • 2016-08-04
      • 2014-05-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-23
      • 1970-01-01
      • 1970-01-01
      • 2012-07-29
      相关资源
      最近更新 更多