2016-02-08 2 views
1

У меня есть консоль C#, которую я сделал в службе Windows, которую я хотел бы выполнять надежно и постоянно.Несколько таймеров/обратных вызовов - лучший подход для предотвращения дублирования и для мониторинга их

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

У него есть несколько аспектов. Каждый работает очень регулярно. Ранее я читал о TaskScheduler vs Windows Service, использующем подобные вещи, и выбрал этот подход, потому что что-то работает почти постоянно.

  • TaskType1
  • TaskType2
  • TaskType3
  • TaskType4

Я использую функции обратного вызова таймера, каждый со своим собственным, подобно этой упрощенной версии:

class Program 
{ 
    static PollingService _service; 

    static void Main() 
    { 
     _service = new PollingService(); 

     TimerCallback tc1 = _service.TaskType1; 
     TimerCallback tc2 = _service.TaskType2; 
     TimerCallback tc3 = _service.TaskType3A; 
     TimerCallback tc4 = _service.TaskType3B; 

     Timer t1 = new Timer(tc1, null, 1000, 5000); 
     Timer t2 = new Timer(tc2, null, 2000, 8000); 
     Timer t3 = new Timer(tc3, null, 3000, 11000); 
     Timer t4 = new Timer(tc4, null, 4000, 13000); 


     Console.WriteLine("Press Q to quit"); 
     while (Console.ReadKey(true).KeyChar != 'q') 
     { 
     } 
    } 
} 

class PollingService 
{ 
    public void TaskType1(object state) 
    { 
     for (int i = 1; i <= 10; i++) 
     { 
      Console.WriteLine($"TaskOne numbering {i}"); 
      Thread.Sleep(100); 
     } 
    } 

    public void TaskType2(object state) 
    { 
     for (int i = 10; i <= 100; i++) 
     { 
      Console.WriteLine($"TaskTwo numbering {i}"); 
      Thread.Sleep(100); 
     } 
    } 

    public void TaskType3A(object state) 
    { 
     Increment(200000000); 
    } 

    public void TaskType3B(object state) 
    { 
     Increment(40000); 
    } 

    private void Increment(int startNumber) 
    { 
     for (int i = startNumber; i <= startNumber + 1000; i++) 
     { 
      Console.WriteLine($"Private {startNumber} numbering {i}"); 
      Thread.Sleep(5); 
     } 
    } 
} 

Во-первых, я хочу, чтобы они не связывались друг с другом, когда они иногда длились долго.
Например. Если задание занимает 20 секунд для запуска, я хочу предотвратить дублирование таймера, пока предыдущий все еще может работать, то же самое для всех таймеров. Например. если t2 работает немного дольше обычного, то не запускайте другое. Я немного читал о if (Monitor.TryEnter(lockObject)), это лучший способ справиться с этим требованием?

Во-вторых, если они оба обращаются к одному и тому же ресурсу (в моем случае - к контексту EF), так что t3 уже использует его, и t4 пытается это сделать. Есть ли способ попросить таймера подождать, пока другой не закончит?

Наконец-то есть способ, которым я могу контролировать эти таймеры/обратные вызовы? Я хотел бы предоставить пользовательский интерфейс, чтобы увидеть состояние этого, когда он работает как служба Windows. Мой эндшпиль заключается в том, чтобы предоставить пользовательский интерфейс, который пользователи могут видеть, если задача запущена, а если нет, то запускайте ее по требованию, если она не будет запущена на некоторое время. Но на одном дыхании не создавайте дубликат во время работы.

Я задался вопросом, следует ли мне задавать эти вопросы как отдельные вопросы, но они кажутся настолько переплетенными с решением друг друга.

ответ

2

Если вам нужно убедиться, что каждый поток не имеет перекрытия, вы можете использовать метод Timer.Change(int, int), чтобы остановить выполнение в начале обратного вызова, а затем возобновить его в конце обратного вызова. Вы также можете сделать магию с ManualResetEvent для каждого потока, но это будет беспорядочно.

Я не поклонник таймеров для потоковой передачи и стараюсь избегать их, когда только могу. Если вы можете пожертвовать «каждой нитью должен бежать после n секунд», сделайте это. Вместо этого используйте задачи с токеном отмены, это решит проблему с перекрытием. Например:

А.

public class Foo 
{ 
    private CancellationTokenSource _cts; 
    //In case you care about what tasks you have. 
    private List<Task> _tasks; 

    public Foo() 
    { 
     this._cts = new CancellationTokenSource(); 

     this._tasks.Add(Task.Factory.StartNew(this.Method1, this._cts.Token)); 
     this._tasks.Add(Task.Factory.StartNew(this.Method2, this._cts.Token)); 
     this._tasks.Add(Task.Factory.StartNew(this.Method3, this._cts.Token)); 
     this._tasks.Add(Task.Factory.StartNew(this.Method4, this._cts.Token)); 


    } 

    private void Method1(object state) 
    { 
     var token = (CancellationToken) state; 
     while (!token.IsCancellationRequested) 
     { 
      //do stuff 
     } 

    } 
    private void Method2(object state) 
    { 
     var token = (CancellationToken)state; 
     while (!token.IsCancellationRequested) 
     { 
      //do stuff 
     } 
    } 
    private void Method3(object state) 
    { 
     var token = (CancellationToken)state; 
     while (!token.IsCancellationRequested) 
     { 
      //do stuff 
     } 
    } 
    private void Method4(object state) 
    { 
     var token = (CancellationToken)state; 
     while (!token.IsCancellationRequested) 
     { 
      //do stuff 
     } 
    } 

    public void StopExecution() 
    { 
     this._cts.Cancel(); 
    } 
} 

ЭФ контекст будет сгенерировано исключение, если используется более одного потока одновременно. Существует способ синхронизации, используя lock. Это будет выглядеть примерно так, учитывая приведенный выше пример:

B.

public class Foo 
{ 
    private object _efLock; 
    public Foo() 
    { 
     this._efLock = new object(); 
    } 
. 
. 
. 
    private void MethodX(object state) 
    { 
     var token = (CancellationToken)state; 
     while (!token.IsCancellationRequested) 
     { 
      lock(this._efLock) 
      { 
       using(....... 
      } 
     } 
    } 
} 

Вы должны будете сделать это в каждом потоке, который обращается ваш контекст EF. Имейте в виду, что снова обслуживание становится раздражающим из-за когнитивной нагрузки, которая идет со сложными сценариями lock.

Недавно я разработал приложение, в котором мне понадобилось несколько потоков для доступа к одному и тому же контексту EF. Как я уже упоминал выше, блокировка стала слишком большой (и было требование производительности), поэтому я разработал решение, в котором каждый поток добавляет свой объект в общую очередь, а отдельный поток ничего не делает, кроме как извлекает данные из очереди и звоните в EF. Таким образом, контекст EF доступен только одним потоком. Задача решена. Вот что это будет выглядеть, учитывая выше пример:

C.

public class Foo 
{ 
    private struct InternalEFData 
    { 
     public int SomeProperty; 
    } 


    private CancellationTokenSource _dataCreatorCts; 
    private CancellationTokenSource _efCts; 

    //In case you care about what tasks you have. 
    private List<Task> _tasks; 
    private Task _entityFrameworkTask; 

    private ConcurrentBag<InternalEFData> _efData; 


    public Foo() 
    { 
     this._efData = new ConcurrentBag<InternalEFData>(); 

     this._dataCreatorCts = new CancellationTokenSource(); 
     this._efCts = new CancellationTokenSource(); 

     this._entityFrameworkTask = Task.Factory.StartNew(this.ProcessEFData, this._efCts.Token); 

     this._tasks.Add(Task.Factory.StartNew(this.Method1, this._dataCreatorCts.Token)); 
     this._tasks.Add(Task.Factory.StartNew(this.Method2, this._dataCreatorCts.Token)); 
     . 
     . 
     . 

    } 

    private void ProcessEFData(object state) 
    { 
     var token = (CancellationToken) state; 
     while (!token.IsCancellationRequested) 
     { 
      InternalEFData item; 
      if (this._efData.TryTake(out item)) 
      { 
       using (var efContext = new MyDbContext()) 
       { 
        //Do processing.  
       } 
      } 
     } 

    } 

    private void Method1(object state) 
    { 
     var token = (CancellationToken) state; 
     while (!token.IsCancellationRequested) 
     { 
      //Get data from whatever source 
      this._efData.Add(new InternalEFData()); 
     } 

    } 

    private void Method2(object state) 
    { 
     var token = (CancellationToken) state; 
     while (!token.IsCancellationRequested) 
     { 
      //Get data from whatever source 
      this._efData.Add(new InternalEFData()); 
     } 
    } 


    public void StopExecution() 
    { 
     this._dataCreatorCts.Cancel(); 
     this._efCts.Cancel(); 
    } 
} 

Когда дело доходит до чтения данных от выполнения темы, я обычно использую SynchronizationContext. Я не знаю, правильно ли это использовать объект, и кто-то еще может прокомментировать это.Создание объекта синхронизации, передать его своим резьб и иметь их обновлять необходимые данные и отправить его в UI/консоли резьбы:

D.

public struct SyncObject 
{ 
    public int SomeField; 
} 

public delegate void SyncHandler(SyncObject s); 

public class Synchronizer 
{ 
    public event SyncHandler OnSynchronization; 

    private SynchronizationContext _context; 

    public Synchronizer() 
    { 
     this._context = new SynchronizationContext(); 
    } 

    public void PostUpdate(SyncObject o) 
    { 
     var handleNullRefs = this.OnSynchronization; 
     if (handleNullRefs != null) 
     { 
      this._context.Post(state => handleNullRefs((SyncObject)state), o); 
     } 
    } 
} 

public class Foo 
{ 
    private Synchronizer _sync; 
    public Foo(Synchronizer s) 
    { 
     this._sync = s; 
    } 
    private void Method1(object state) 
    { 
     var token = (CancellationToken) state; 
     while (!token.IsCancellationRequested) 
     { 
      //do things 
      this._sync.PostUpdate(new SyncObject()); 
     } 

    } 
} 

Опять же, как я делаю это, я не знаю, правильно ли это.

+0

Спасибо @Brandon, я работаю над этим. Как я могу добиться задержки, скажем, я хочу, чтобы Method1 работал только каждые 10 минут? Должен ли я держать поток открытым с помощью Thread.Sleep? –

+0

Поток всегда будет работать, предполагая, что вы не вызываете отмену. Если мы говорим о ** A. ** выше (я написал их для удобства идентификации), первая строка, которую вы имели бы после циклов while, была бы условием 'if', определенным для этого цикла, который скажет вам независимо от того, действительно ли вам нужно что-то обрабатывать. Проверьте очередь/буфер, логическое поле, состояние какого-либо объекта или много чего. Что-то должно быть в состоянии сказать вам, должен ли этот поток выполнять какую-либо фактическую работу. – Brandon

0
  1. в основном, да, или AutoResetEvent
  2. вы можете остановить его, ждать ресурс, чтобы освободить, а затем перезапустить его
  3. сохранить список состояний, связанных с таймерами, а также обновлять эти состояния от таймеры (настроены на запуск при запуске, ожидание при завершении или что-то в этом направлении)
0

1: Вероятно, лучше всего ничего не делать в таймерах, но ставить задачу - ЕСЛИ любой КОГДА флаг установлен или не установлен. Найдите блокировку (класс) о том, как реализовать это без блокировки.

2: Монитор. Но серьезно, почему они разделяют конфликт EF?

3: Конечно. Создайте счетчики производительности. Контролируйте их. API находится в окнах много лет.

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