2013-12-21 2 views
3

Я хотел выполнить ряд задач, выполняемых каждый с таймаутом его собственного.Создание задач с таймаутами

Я заимствовал метод расширения для создания задач с таймаутами здесь 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.

Edit:

Обеспечение того, что истекло задача не делать ничего после периода таймаута т.е. отмены задачи в целом.

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 

ответ

2

Output содержит три значения, так как программа ожидает, чтобы все задачи 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(_ => LongRunningTask("Entering task1"), TaskCreationOptions.AttachedToParent) и Исключение было брошено на другую тему. Это исключение не влияет на LongRunningTask

Edit:

Там несколько вариантов:

  1. Вызов t1.Wait exception'll повторно брошено сразу, и вы можете отменить задачу
  2. Звонок TimeoutAfter(10) до Продолжить С

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

Continue будет выполняться только после завершения TimeoutAfter и LongRunningTask, но вы должны обновить TimeoutAfter, вы, чтобы вернуть Task<Result> не 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; 
    } 
+0

Как я могу сказать задачу, чтобы остановить выполнение после того, как тайм-аут произошел, так что он ничего не добавляет к выходу в то время как работает в фоновом режиме. Поставка его с использованием источника токена отмены, а затем его отмена не работает. –

+0

В коде только 'Task.WaitAll' перебрасывает TimoutException, все задачи уже завершены и' cts.Cancel() 'не имеет значения. Поместите 'cts.Cancel()' перед 'Task.WaitAll', и задача будет принудительной. – GSerjo

+0

Это отменит задачу независимо от тайм-аута. Поведение, которое я хочу, - это отменить задачу в случае тайм-аута или исключения. –

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 будет представлять собой продолжение и поэтому он будет нарушенными, если происходит тайм-аут (и ожидания на нем будет сгенерировано исключение). Если вы этого не хотите, проверьте состояние t в продолжении до доступа к его Result.

Кроме того, вы никогда не должны вызывать Add() на List, как это, потому что Add() не поточно-и есть шанс, что несколько потоков будет пытаться добавлять к нему одновременно. Чтобы этого избежать, используйте либо одну из параллельных коллекций, либо блокировку.

0

Просто ради эталонным, я в конечном итоге делает что-то вроде этого

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

Кстати, я думаю, вы должны использовать '_', только если вы * не используете этот параметр. Если вы его используете, это должно быть нечто вроде «antecedent», 'task' или, по крайней мере,' t'. – svick

Смежные вопросы