7
votes

Minuteries non réentrantes

J'ai une fonction que je veux invoquer tous les x secondes, mais je veux que ce soit sur le thread-coffre-fort.

Puis-je mettre en place ce comportement lorsque je crée la minuterie? (Ça ne me dérange pas que .NET TIMERIER J'utilise, je veux juste que ce soit sur le fil).

Je sais que je peux implémenter des serrures à l'intérieur de ma fonction de rappel, mais je pense qu'il serait plus élégant s'il était au niveau du minuteur.

ma fonction de rappel et environnement ne sont pas liés à un UI.

[modifier 1] Je ne veux tout simplement pas qu'il y ait plus d'un fil à l'intérieur de ma fonction de rappel.

[modifier 2] Je veux garder le verrouillage à l'intérieur du niveau de la minuterie, car la minuterie est responsable du moment où appeler mon rappel, et voici une situation particulière lorsque je ne veux pas appeler ma fonction de rappel. Donc, je pense que quand appeler est la responsabilité de la minuterie .


4 commentaires

Vous ne donnez pas assez d'informations ... Votre fonction accède à toute autre partie des accès du programme? Sinon, il est déjà threadsafe (avec et sans minuterie) ... Si oui, la minuterie n'a rien avec la threadsafety, mais vous devez utiliser une molette, etc.


Pourquoi voulez-vous garder le verrouillage de la fonction de rappel exactement?


Votre édition 2 n'a pas de sens: la minuterie est responsable d'appeler votre fonction à temps, votre fonction est responsable de fonctionner correctement avec vos données partagées (location, etc.) - l'heure ne peut être responsable de vos données, de votre fonction est...


La minuterie elle-même est threadsafe - votre code peut ne pas être, mais les informations données ne suffisent pas pour répondre à la manière de le faire threadsafe.


5 Réponses :


2
votes

Je sais que je peux implémenter des serrures à l'intérieur de ma fonction de rappel, mais je pense qu'il sera plus élégant s'il sera au niveau de la minuterie

Si le verrouillage est nécessaire, alors comment une minuterie pourrait-elle arranger cela? Vous recherchez une Freebie magique.

re édition1:

Vos choix sont System.Timers.Timer et System.threading.Timer, les deux ont besoin de précautions contre la nouvelle entrée. Voir Cette page et recherchez la traiter avec la minuterie Réentruisance de l'événement Section.


2 commentaires

Voir mon édition 1, est-ce que ça a du sens pour vous?


@HANK, j'ai lu la section que vous avez mentionnée, j'ai aimé votre réponse, mais la chibacité a donné ma réponse plus appropriée à ma question, car je veux le gérer à l'intérieur du réglage de la minuterie .. Et ne pas entrer dans la logique à l'intérieur du rappel Funciton, merci encore.



0
votes

Comment la minuterie peut savoir sur vos données partagées?

Le rappel de la minuterie est exécuté sur un fil threadpool. Donc, vous aurez au moins 2 threads:

  1. Votre fil principal où la minuterie est créée et lancée;
  2. thread de threadpool pour lancer le rappel.

    Et il est de votre responsabilité de fournir des travaux corrects avec vos données partagées.

    re modifications: La chibacité fournie l'exemple parfait.


0 commentaires

21
votes

Je suppose que votre question n'est pas tout à fait claire, que vous souhaitez vous assurer que votre minuterie ne peut pas rentrer dans votre rappel pendant que vous traitez un rappel et que vous souhaitez le faire sans verrouillage. Vous pouvez y parvenir en utilisant un system.timers.timer code> et en veillant à ce que la propriété autoréforme code> est définie sur false. Cela garantira que vous devez déclencher la minuterie de chaque intervalle manuellement, empêchant ainsi toute réentrancement:

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();
        }
    }
} 


2 commentaires

Vous comprenez parfaitement ma question et aimé votre solution, j'espérais que ce comportement dans hérité de l'une des classes de la minuterie, mais c'est assez bon ..


C'est précisément ce que j'ai fait et cela fonctionne bien. FYI-Cet exemple serait un peu plus clair si vous n'avez pas utilisé de délégué anonyme.



6
votes

Complétant la solution de Tim Lloyd pour System.timers.Timer code>, voici une solution pour empêcher la réentruisance des cas où vous souhaitez utiliser system.threading.Timer code>.

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();
    }
}


1 commentaires

Belle classe d'emballage, merci! Un peu d'amélioration: il y a déjà un timeout.infinititespan champ statique.



1
votes
using System;
using System.Diagnostics;

/// <summary>
///     Updated the code.
/// </summary>
public class NicerFormTimer : IDisposable {

    public void Dispose() {
        using ( this.Timer ) { }

        GC.SuppressFinalize( this );
    }

    private System.Windows.Forms.Timer Timer { get; }

    /// <summary>
    ///     Perform an <paramref name="action" /> after the given interval (in <paramref name="milliseconds" />).
    /// </summary>
    /// <param name="action"></param>
    /// <param name="repeat">Perform the <paramref name="action" /> again. (Restarts the <see cref="Timer" />.)</param>
    /// <param name="milliseconds"></param>
    public NicerFormTimer( Action action, Boolean repeat, Int32? milliseconds = null ) {
        if ( action == null ) {
            return;
        }

        this.Timer = new System.Windows.Forms.Timer {
            Interval = milliseconds.GetValueOrDefault( 1000 )
        };

        this.Timer.Tick += ( sender, args ) => {
            try {
                this.Timer.Stop();
                action();
            }
            catch ( Exception exception ) {
                Debug.WriteLine( exception );
            }
            finally {
                if ( repeat ) {
                    this.Timer.Start();
                }
            }
        };

        this.Timer.Start();
    }

}

/// <summary>
///     Updated the code.
/// </summary>
public class NicerSystemTimer : IDisposable {

    public void Dispose() {
        using ( this.Timer ) { }

        GC.SuppressFinalize( this );
    }

    private System.Timers.Timer Timer { get; }

    /// <summary>
    ///     Perform an <paramref name="action" /> after the given interval (in <paramref name="milliseconds" />).
    /// </summary>
    /// <param name="action"></param>
    /// <param name="repeat">Perform the <paramref name="action" /> again. (Restarts the <see cref="Timer" />.)</param>
    /// <param name="milliseconds"></param>
    public NicerSystemTimer( Action action, Boolean repeat, Double? milliseconds = null ) {
        if ( action == null ) {
            return;
        }

        this.Timer = new System.Timers.Timer {
            AutoReset = false,
            Interval = milliseconds.GetValueOrDefault( 1000 )
        };

        this.Timer.Elapsed += ( sender, args ) => {
            try {
                this.Timer.Stop();
                action();
            }
            catch ( Exception exception ) {
                Debug.WriteLine( exception );
            }
            finally {
                if ( repeat ) {
                    this.Timer.Start();
                }
            }
        };

        this.Timer.Start();
    }

}

0 commentaires