2

У меня есть метод, который иногда занимает больше минуты, чтобы выполнить. Я хочу создать задачу для мониторинга времени, которое требуется для выполнения этого метода. Если метод выполняется с-в 2 минуты, я должен вернуть вывод первой задачи, иначе я должен выбросить исключение. Я использую .net framework 4.0 с C# в качестве языка.Создайте две задачи в C# .net и закройте один из них через определенный период времени

В этом случае я не могу использовать Microsoft Reactive Extensions, так как он освобождает основную нить. Я не хочу, чтобы освободить основной поток, пока происходит следующее

  1. тайм-аут
  2. данных возвращается
  3. Любое другое исключение происходит

Просьба представить свои предложения.

ответ

2

В идеале, вы должны использовать CancellationTokenSource с тайм-аутом и наблюдать его CancellationToken внутри ваш метод. Если это невозможно, вы можете использовать Task.WhenAny. Следующая реализация MethodAsync должна соответствовать своему сценарию для .NET 4.0:

using System; 
using System.Threading; 
using System.Threading.Tasks; 

namespace ConsoleApp 
{ 
    class Program 
    { 
     // the method to monitor and timeout 
     int Method() 
     { 
      Thread.Sleep(3000); // sleep for 3s 
      return 42; 
     } 

     Task<int> MethodAsync(int timeout) 
     { 
      // start the Method task 
      var task1 = Task.Run(() => Method()); 

      // start the timeout task 
      var task2 = Task.Delay(timeout); 

      // either task1 or task2 
      return Task.WhenAny(task1, task2).ContinueWith((task) => 
      { 
       if (task.Result == task1) 
        return task1.Result; 
       throw new TaskCanceledException(); 
      }); 
     } 

     // The entry of the console app 
     static void Main(string[] args) 
     { 
      try 
      { 
       // timeout in 6s 
       int result = new Program().MethodAsync(6000).Result; 
       Console.WriteLine("Result: " + result); 
      } 
      catch (Exception ex) 
      { 
       Console.WriteLine("Error: " + ex.Message); 
      } 
      Console.WriteLine("Hit enter to exit."); 
      Console.ReadLine(); 
     } 
    } 
} 

Ниже приводится версия MethodAsync с помощью async/await, если вы можете использовать Microsoft.Bcl.Async:

async Task<int> MethodAsync(int timeout) 
{ 
    // start the Method task 
    var task1 = Task.Run(() => Method()); 

    // start the timeout task 
    var task2 = Task.Delay(timeout); 

    // either task1 or task2 
    var task = await TaskEx.WhenAny(task1, task2); 
    if (task == task1) 
     return task1.Result; 

    throw new TaskCanceledException(); 
} 
+0

@Brown_Dynamite, у вас есть несколько вариантов: ** 1) ** просто назовите его следующим образом: 'var result = MethodAsync (timeout: 1000) .Result;' - this будет блокировать основной поток. ** 2) ** альтернативно: 'MethodAsync (timeout: 1000) .Wait();' ** 3) ** удалить 'async/await' и сделать' Task.WaitAny' вместо 'TaskEx.WhenAny' внутри кода Я отправил. – Noseratio

+0

Я выполнил код, который вы использовали после внесения этих двух изменений. 1) Период ожидания для задачи 1 задан равным 3000, а задание 2 задано как 6000. Но задача 2 выполняется сначала, и в результате возникает TaskCanceledException - – SharpCoder

+0

@Brown_Dynamite, действительно, в версии 'ContinueWith' была непонятная ошибка, обратите внимание на изменение: 'task.Result == task1'. Попробуй это сейчас. В версии 'async/await' не было этой ошибки. – Noseratio

0

Вы можете использовать класс TimeOut, который вы используете, чтобы бросить TimeoutException по истечении заданного времени. Что-то вроде:

const int twoMinutes = 120000; 
TimeOutHelper.ExecuteMethod(() => 
{ 
    byte[] buffer = new byte[2048]; 
    int c = -1; 

    do 
    { 
     c = Service.Read(buffer, 0, 2048); 
    } while (c == 0); 

}, twoMinutes); 

Код:

public static class TimeOutHelper 
{ 
    private class ObjectState 
    { 
     public Action predict { get; set; } 
     public AutoResetEvent autoEvent { get; set; } 
    } 

    public static bool ExecuteMethod(Action predict, int timeOutInterval, int retryCounter = 1) 
    { 
     bool IsFinished = false; 
     ObjectState objectState = new ObjectState(); 
     AutoResetEvent autoEvent = new AutoResetEvent(false); 
     int timeOutInt = timeOutInterval > 0 ? timeOutInterval: Timeout.Infinite; 
     objectState.autoEvent = autoEvent; 
     objectState.predict = predict; 
     Timer timer = null; 

     var callbackMethod = new TimerCallback(TimerCallbackMethod); 
     timer = new Timer(callbackMethod, objectState, 0, timeOutInt+5); 

     try 
     { 
      for (int i = 0; i < retryCounter; i++) 
      { 
       var isSignal = autoEvent.WaitOne(timeOutInt, false); 
       if (isSignal) 
       break; 
       if (retryCounter - 1 == i) throw new TimeoutException(); 
       else 
        timer.Change(0, timeOutInt); 
      } 
     } 
     finally 
     { 
      IsFinished = true; 

      if (autoEvent != null) 
       autoEvent.Dispose(); 
      if (timer != null) 
       timer.Dispose(); 
     } 
     return IsFinished; 
    } 

    private static void TimerCallbackMethod(object state) 
    { 
     var objectSate = (ObjectState)state; 
     var predict = (Action)objectSate.predict; 
     predict(); 
     if(objectSate!=null && !objectSate.autoEvent.SafeWaitHandle.IsClosed) 
      objectSate.autoEvent.Set(); 
    } 
} 
0

вы можете использовать таймер как нить.

System.Threading.Timer timer; 
timer = new System.Threading.Timer(new TimerCallback(Module_Watchdog), null, 0, 1000); 

Модуль_Watchdog() выполняется через определенные интервалы (здесь 1000 мс). Обратите внимание, что Module_Watchdog() не выполняется в потоке, который создал таймер; он выполняется в потоке ThreadPool, поставляемом системой.

private void Module_Watchdog(object obj) 
{ 
    // Block here output of task1 as timeout already occured.. 
    try 
    { 
    throw new TimeoutException(); 
    } 
    finally 
    { 

    } 
} 

и окончательная реализация будет выглядеть следующим образом:

... 
... 
timer = new System.Threading.Timer(new TimerCallback(Module_Watchdog), null, 0, 1000); 
Moduleunder_Watchdog(); 
timer.Change(Timeout.Infinite, Timeout.Infinite); //disable 

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

timer.Change(0, 1000); //enable 

Если вам нужно прочитать значение времени, которое вы можете использовать, используйте секундомер. Более подробная информации о секундомере может быть http://msdn.microsoft.com/en-us/library/system.diagnostics.stopwatch.aspx

2

Я хотел бы посмотреть на использование Microsoft, реактивным Фреймворк. Это было бы очень легко.

Здесь:

var query = 
    Observable 
     .Start(() => longRunningTask()) 
     .Timeout(TimeSpan.FromMinutes(2.0)); 

Вы тогда просто подписаться на наблюдаемый, чтобы получить результат из.

query.Subscribe(
    n => Console.WriteLine(n), 
    ex => Console.WriteLine(ex.Message)); 

Всё. Вы либо получаете результат - n - или вы получаете исключение - ex. Просто как тот. И все это работает в фоновом задании для вас.

Используйте этот вид кода, чтобы завершить длительное вычисление по таймауту.

var query = 
    Observable 
     .Create<int>(o => 
      Scheduler.Default.AsLongRunning().ScheduleLongRunning(c => 
      { 
       for (var i = 0; i < 100; i++) 
       { 
        Thread.Sleep(100); 
        if (c.IsDisposed) 
        { 
         break; 
        } 
       } 
       if (!c.IsDisposed) 
       { 
        o.OnNext(42); 
       } 
       o.OnCompleted(); 
      })) 
     .Timeout(TimeSpan.FromMinutes(2.0)); 

Главное, чтобы понять, с Rx, что отмена основаны на интерфейсе IDisposable. Фактически метод Subscribe возвращает IDisposable, что позволяет подписчивому коду рано прекратить подписку Rx. Но, когда наблюдаемый завершается (либо с OnComplete или OnError затем основным одноразовым расположено. Когда есть цепь операторов, то каждый располагаемая в цепи расположены.

Так что этот код возвращает только возвращает одноразовый, который когда-то утилизировать закончит вычисление. Достаточно просто.

+0

С RX будет 'longRunningTask' продолжать выполнение в любом случае после таймаута? Я предполагаю, что это будет, для чего потребуется нечто вроде 'Thread.Abort()', не так ли? – avo

+1

@avo - Вы правы. Rx не останавливает фоновые задачи. Он чисто игнорирует результат, но не останавливает код. Это имеет смысл, поскольку он знает способ узнать, что прервать поток будет делать ваша программа. Это может быть на полпути с помощью операции записи, например. Код должен быть написан, чтобы быть в курсе, что он должен завершиться. Я добавлю пример кода, который будет правильно завершен. – Enigmativity

+0

@ Энигматичность: Благодарим вас за ответ. Но использование Microsoft Reactive extensions приведет к выпуску основного потока. Он не будет ждать таймаута, или он не будет ждать, пока метод вернет фактические данные. Это решение не подходит для моей проблемы. – SharpCoder