2011-08-14 13 views
5

Mam funkcję, którą chcę wywołać co x sekund, ale chcę, aby była bezpieczna dla wątków.Nierezydentne liczniki czasu

Czy mogę ustawić to zachowanie podczas tworzenia zegara? (Nie mam nic przeciwko temu .NET timera, którego używam, po prostu chcę, aby był bezpieczny dla wątków).

Wiem, że mogę zaimplementować blokady wewnątrz funkcji wywołania zwrotnego, ale myślę, że byłoby to bardziej eleganckie, gdyby znajdowało się na poziomie timera.

Moja funkcja zwrotna i środowisko nie są powiązane z interfejsem użytkownika.

[Edytuj 1] Po prostu nie chcę, aby w mojej funkcji zwrotnej znajdował się więcej niż jeden wątek.

[Edycja 2] chcę zachować blokadę wewnątrz poziomie timera, bo zegar jest odpowiedzialny za kiedy zadzwonić do wywołania zwrotnego, a tu istnieje szczególna sytuacja, kiedy nie chcę zadzwonić do mojego funkcja zwrotna. Więc myślę, że kiedy zadzwonić, to odpowiedzialność czasomierza.

+2

nie dają wystarczająco dużo informacji ... Czy Twój cokolwiek dostęp funkcja jakakolwiek inna część programu ma dostęp? JEŚLI nie, to już jest Threadafe (z i bez timera) ... JEŚLI tak, to timer nie ma nic z threadafety, ale trzeba użyć blokady itp. – Yahia

+1

** dlaczego ** czy chcesz zachować blokowanie funkcji zwrotnej? –

+0

@Paul - zobacz moją edycję 2 – Delashmate

Odpowiedz

16

Zgaduję, ponieważ twoje pytanie nie jest do końca jasne, że chcesz się upewnić, że licznik czasu nie może ponownie wprowadzić twojego oddzwonienia podczas przetwarzania wywołania zwrotnego i chcesz to zrobić bez blokowania. Możesz to osiągnąć, używając System.Timers.Timer i zapewniając, że właściwość AutoReset jest ustawiona na false.Zapewni to, że trzeba wywołać timer na każdym przedziale ręcznie, zapobiegając w ten sposób ponowne wejścia:

public class NoLockTimer : IDisposable 
{ 
    private readonly Timer _timer; 

    public NoLockTimer() 
    { 
     _timer = new Timer { AutoReset = false, Interval = 1000 }; 

     _timer.Elapsed += delegate 
     { 
      //Do some stuff 

      _timer.Start(); // <- Manual restart. 
     }; 

     _timer.Start(); 
    } 

    public void Dispose() 
    { 
     if (_timer != null) 
     { 
      _timer.Dispose(); 
     } 
    } 
} 
+0

w pełni rozumiem moje zadanie i podobało mi się twoje rozwiązanie, miałem nadzieję, że to zachowanie w inhereted wewnątrz jednej z klasy timerów, ale to jest wystarczająco dobre .. – Delashmate

+0

To jest dokładnie to, co zrobiłem i działa świetnie. FYI - ten przykład będzie nieco jaśniejszy, jeśli nie używałbyś anonimowego delegata. –

2

wiem, że mogę realizować blokad wewnątrz mojej funkcji wywołania zwrotnego, ale myślę, że to będzie bardziej elegancki, jeśli to będzie na poziomie timera

Jeśli zamek jest konieczne wtedy, jak można czasomierz zaaranżować ? Szukasz magicznego freebie.

Re Edit1:

Twoje wybory są System.Timers.Timer i System.Threading.Timer, obie potrzebują środków ostrożności przed ponownym wejściem. Zobacz this page i poszukaj sekcji Radzenie sobie z Timerem z wydarzeniem Reentrance.

+0

Zobacz moją edycję 1, czy ma to dla ciebie sens? – Delashmate

+0

@Hank Czytałem sekcję, o której wspomniałeś, podobała mi się odpowiedź, ale chibacity podarowała mi bardziej odpowiednią odpowiedź na moje pytanie, ponieważ chcę zarządzać nią w ustawieniach timera .. i nie wchodzić w logikę wewnątrz funkcji callback, dzięki jeszcze raz – Delashmate

-1

Jak zegar może wiedzieć o twoich udostępnionych danych?

Funkcja wywołania zwrotnego timera jest wykonywana na niektórych wątkach ThreadPool. Będziesz mieć co najmniej 2 wątki:

  1. Twój główny wątek, w którym czasomierz jest tworzony i uruchamiany;
  2. Wątek z ThreadPool do wywołania zwrotnego.

Twoim zadaniem jest zapewnienie poprawnej pracy z udostępnionymi danymi.

Ponowna edycja: chibacity stanowiło doskonały przykład.

+0

@downvoter: Co jest nie tak z moją odpowiedzią? –

3

Uzupełniając rozwiązania Tim Lloyd za System.Timers.Timer, oto rozwiązanie, aby zapobiec ponowne wejścia w przypadkach, gdy chcesz użyć System.Threading.Timer zamiast.

TimeSpan DISABLED_TIME_SPAN = TimeSpan.FromMilliseconds(-1); 

TimeSpan interval = TimeSpan.FromSeconds(1); 
Timer timer = null; // assign null so we can access it inside the lambda 

timer = new Timer(callback: state => 
{ 
    doSomeWork(); 
    try 
    { 
    timer.Change(interval, DISABLED_TIME_SPAN); 
    } 
    catch (ObjectDisposedException timerHasBeenDisposed) 
    { 
    } 
}, state: null, dueTime: interval, period: DISABLED_TIME_SPAN); 

wierzę, że nie chcesz interval być dostępne wewnątrz wywołania zwrotnego, ale to łatwo naprawić, jeśli chcesz: Put wyżej w NonReentrantTimer klasy, która owija Timer klasy plc jest. Następnie można przekazać wywołanie zwrotne doSomeWork jako parametr. Przykładem takiej klasy:

public class NonReentrantTimer : IDisposable 
{ 
    private readonly TimerCallback _callback; 
    private readonly TimeSpan _period; 
    private readonly Timer _timer; 

    public NonReentrantTimer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period) 
    { 
     _callback = callback; 
     _period = period; 
     _timer = new Timer(Callback, state, dueTime, DISABLED_TIME_SPAN); 
    } 

    private void Callback(object state) 
    { 
     _callback(state); 
     try 
     { 
      _timer.Change(_period, DISABLED_TIME_SPAN); 
     } 
     catch (ObjectDisposedException timerHasBeenDisposed) 
     { 
     } 
    } 


    public void Dispose() 
    { 
     _timer.Dispose(); 
    } 
} 
0
/// <summary> 
/// The C#6 version 
/// </summary> 
public class TimerNicer : IDisposable { 
    private Action OnElapsed { get; } 

    [NotNull] 
    private System.Timers.Timer Timer { get; } = new System.Timers.Timer { AutoReset = false, Interval = 1 }; 

    public TimerNicer(Action onElapsed) { 

     this.OnElapsed = onElapsed ?? (() => { 
     }); 

     this.Timer.Elapsed += (sender, args) => { 

      this.Timer.Stop(); // Why not stop the timer here with this? 

      try { 
       this.OnElapsed(); // do stuff here 
      } 
      catch (Exception exception) { 
       Console.WriteLine(exception); 
      } 
      finally { 
       this.Timer.Start(); 
      } 
     }; 

     this.Timer.Start(); 
    } 

    public void Dispose() => this.Timer.Dispose(); 
} 
Powiązane problemy