web-dev-qa-db-fra.com

C # exécute un thread toutes les X minutes, mais seulement si ce thread ne s'exécute pas déjà

J'ai un programme C # qui a besoin d'envoyer un thread toutes les X minutes, mais seulement si le thread précédemment envoyé (à partir de X minutes) ne fonctionne pas encore

Une vieille Timer seule ne fonctionnera pas (car elle distribue un événement toutes les X minutes, que le processus précédemment envoyé soit terminé ou non.).

Le processus qui va être envoyé varie énormément dans le temps requis pour effectuer sa tâche - parfois, cela peut prendre une seconde, parfois plusieurs heures. Je ne veux pas recommencer le processus s'il est toujours en cours depuis la dernière fois. 

Quelqu'un peut-il fournir des exemples de code de travail C #?

60
Keith Palmer Jr.

À mon avis, dans ce cas, il est préférable d’utiliser la classe System.ComponentModel.BackgroundWorker, puis de vérifier sa propriété IsBusy à chaque fois que vous souhaitez expédier (ou non) le nouveau thread. Le code est assez simple. voici un exemple:

class MyClass
{    
    private BackgroundWorker worker;

    public MyClass()
    {
        worker = new BackgroundWorker();
        worker.DoWork += worker_DoWork;
        Timer timer = new Timer(1000);
        timer.Elapsed += timer_Elapsed;
        timer.Start();
    }

    void timer_Elapsed(object sender, ElapsedEventArgs e)
    {
        if(!worker.IsBusy)
            worker.RunWorkerAsync();
    }

    void worker_DoWork(object sender, DoWorkEventArgs e)
    {
        //whatever You want the background thread to do...
    }
}

Dans cet exemple, j’ai utilisé System.Timers.Timer, mais j’estime que cela devrait également fonctionner avec d’autres minuteries. La classe BackgroundWorker prend également en charge le rapport d'avancement et l'annulation, et utilise un modèle de communication piloté par les événements avec le thread de dispatching, pour que vous n'ayez pas à vous soucier des variables volatiles, etc.

MODIFIER

Voici un exemple plus élaboré comprenant l'annulation et le rapport d'avancement:

class MyClass
{    
    private BackgroundWorker worker;

    public MyClass()
    {
        worker = new BackgroundWorker()
        {
            WorkerSupportsCancellation = true,
            WorkerReportsProgress = true
        };
        worker.DoWork += worker_DoWork;
        worker.ProgressChanged += worker_ProgressChanged;
        worker.RunWorkerCompleted += worker_RunWorkerCompleted;

        Timer timer = new Timer(1000);
        timer.Elapsed += timer_Elapsed;
        timer.Start();
    }

    void timer_Elapsed(object sender, ElapsedEventArgs e)
    {
        if(!worker.IsBusy)
            worker.RunWorkerAsync();
    }

    void worker_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker w = (BackgroundWorker)sender;

        while(/*condition*/)
        {
            //check if cancellation was requested
            if(w.CancellationPending)
            {
                //take any necessary action upon cancelling (rollback, etc.)

                //notify the RunWorkerCompleted event handler
                //that the operation was cancelled
                e.Cancel = true; 
                return;
            }

            //report progress; this method has an overload which can also take
            //custom object (usually representing state) as an argument
            w.ReportProgress(/*percentage*/);

            //do whatever You want the background thread to do...
        }
    }

    void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        //display the progress using e.ProgressPercentage and/or e.UserState
    }

    void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        if(e.Cancelled)
        {
            //do something
        }
        else
        {
            //do something else
        }
    }
}

Ensuite, pour annuler toute exécution ultérieure, appelez simplement worker.CancelAsync(). Notez qu'il s'agit d'un mécanisme d'annulation entièrement géré par l'utilisateur (il ne prend pas en charge l'abandon de threads ni quoi que ce soit du genre).

54
Grx70

Vous pouvez simplement maintenir un bool volatil pour réaliser ce que vous avez demandé:

private volatile bool _executing;

private void TimerElapsed(object state)
{
    if (_executing)
        return;

    _executing = true;

    try
    {
        // do the real work here
    }
    catch (Exception e)
    {
        // handle your error
    }
    finally
    {
        _executing = false;
    }
}
20
Matt Johnson

Vous pouvez désactiver et activer votre minuterie dans le rappel écoulé.

public void TimerElapsed(object sender, EventArgs e)
{
  _timer.Stop();

  //Do Work

  _timer.Start();
}
8
scottm

Vous pouvez simplement utiliser le System.Threading.Timer et simplement définir la Timeout sur Infinite avant de traiter vos données/méthodes, puis, une fois l'opération terminée, redémarrez la Timer prête pour le prochain appel.

    private System.Threading.Timer _timerThread;
    private int _period = 2000;

    public MainWindow()
    {
        InitializeComponent();

        _timerThread = new System.Threading.Timer((o) =>
         {
             // Stop the timer;
             _timerThread.Change(-1, -1);

             // Process your data
             ProcessData();

             // start timer again (BeginTime, Interval)
             _timerThread.Change(_period, _period);
         }, null, 0, _period);
    }

    private void ProcessData()
    {
        // do stuff;
    }
5
sa_ddam213

Utiliser PeriodicTaskFactory à partir de mon post ici

CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();

Task task = PeriodicTaskFactory.Start(() =>
{
    Console.WriteLine(DateTime.Now);
    Thread.Sleep(5000);
}, intervalInMilliseconds: 1000, synchronous: true, cancelToken: cancellationTokenSource.Token);

Console.WriteLine("Press any key to stop iterations...");
Console.ReadKey(true);

cancellationTokenSource.Cancel();

Console.WriteLine("Waiting for the task to complete...");

Task.WaitAny(task);

La sortie ci-dessous montre que, même si l'intervalle est défini sur 1000 millisecondes, chaque itération ne commence pas tant que le travail de l'action de tâche n'est pas terminé. Ceci est accompli en utilisant le paramètre optionnel synchronous: true.

Press any key to stop iterations...
9/6/2013 1:01:52 PM
9/6/2013 1:01:58 PM
9/6/2013 1:02:04 PM
9/6/2013 1:02:10 PM
9/6/2013 1:02:16 PM
Waiting for the task to complete...
Press any key to continue . . .

METTRE À JOUR

Si vous voulez le comportement "événement ignoré" avec PeriodicTaskFactory, n'utilisez pas l'option synchrone et implémentez Monitor.TryEnter comme ce que Bob a fait ici https://stackoverflow.com/a/18665948/222434

Task task = PeriodicTaskFactory.Start(() =>
{
    if (!Monitor.TryEnter(_locker)) { return; }  // Don't let  multiple threads in here at the same time.

    try
    {
        Console.WriteLine(DateTime.Now);
        Thread.Sleep(5000);
    }
    finally
    {
        Monitor.Exit(_locker);
    }

}, intervalInMilliseconds: 1000, synchronous: false, cancelToken: cancellationTokenSource.Token);

La bonne chose à propos de PeriodicTaskFactory est qu’une tâche est renvoyée et peut être utilisée avec toutes les API TPL, par exemple. Task.Wait, continuations, etc.

4
Jim

Si vous souhaitez que le rappel de la minuterie se déclenche sur un thread en arrière-plan, vous pouvez utiliser un fichier System.Threading.Timer . Cette classe Timer vous permet de "spécifier Timeout.Infinite pour désactiver la signalisation périodique". dans le cadre du constructeur , ce qui fait que la minuterie ne se déclenche qu’une seule fois.

Vous pouvez ensuite créer un nouveau minuteur lorsque le rappel de votre premier minuteur est lancé et terminé, empêchant ainsi la planification de plusieurs minuteurs tant que vous n'êtes pas prêt à les exécuter.

L'avantage ici est que vous ne créez pas de minuteries, puis les annulez à plusieurs reprises, car vous ne planifiez jamais plus que votre "prochain événement" à la fois.

3
Reed Copsey

Il existe au moins 20 méthodes différentes pour y parvenir: utilisation d'un minuteur et d'un sémaphore, variables volatiles, utilisation de la TPL, utilisation d'un outil de planification opensource tel que Quartz, etc. 

Créer un fil de discussion est un exercice coûteux, alors pourquoi ne pas simplement créer ONE et le laisser fonctionner en arrière-plan, car il passera la majorité de son temps au ralenti, cela ne causera pas de véritable ralentissement du système. Réveillez-vous périodiquement et travaillez, puis retournez vous dormir pendant un certain temps. Quelle que soit la durée de la tâche, vous attendez toujours au moins la durée "waitForWork" après avoir terminé avant de commencer une nouvelle tâche.

    //wait 5 seconds for testing purposes
    static TimeSpan waitForWork = new TimeSpan(0, 0, 0, 5, 0);
    static ManualResetEventSlim shutdownEvent = new ManualResetEventSlim(false);
    static void Main(string[] args)
    {
        System.Threading.Thread thread = new Thread(DoWork);
        thread.Name = "My Worker Thread, Dude";
        thread.Start();

        Console.ReadLine();
        shutdownEvent.Set();
        thread.Join();
    }

    public static void DoWork()
    {
        do
        {
            //wait for work timeout or shudown event notification
            shutdownEvent.Wait(waitForWork);

            //if shutting down, exit the thread
            if(shutdownEvent.IsSet)
                return;

            //TODO: Do Work here


        } while (true);

    }
3
Keith

Vous pouvez utiliser System.Threading.Timer. L'astuce consiste à définir l'heure initiale uniquement. L'heure initiale est à nouveau définie lorsque l'intervalle précédent est terminé ou lorsque le travail est terminé (cela se produit lorsque le travail prend plus de temps que l'intervalle). Voici l exemple de code.

class Program
{


    static System.Threading.Timer timer;
    static bool workAvailable = false;
    static int timeInMs = 5000;
    static object o = new object(); 

    static void Main(string[] args)
    {
        timer = new Timer((o) =>
            {
                try
                {
                    if (workAvailable)
                    {
                        // do the work,   whatever is required.
                        // if another thread is started use Thread.Join to wait for the thread to finish
                    }
                }
                catch (Exception)
                {
                    // handle
                }
                finally
                {
                    // only set the initial time, do not set the recurring time
                    timer.Change(timeInMs, Timeout.Infinite);
                }
            });

        // only set the initial time, do not set the recurring time
        timer.Change(timeInMs, Timeout.Infinite);
    }
3
Umer Azaz

Pourquoi ne pas utiliser une minuterie avec Monitor.TryEnter()? Si OnTimerElapsed() est appelé à nouveau avant la fin du thread précédent, il sera simplement ignoré et une autre tentative ne se reproduira pas tant que le minuteur ne se sera pas déclenché.

private static readonly object _locker = new object();

    private void OnTimerElapsed(object sender, ElapsedEventArgs e)
    {
        if (!Monitor.TryEnter(_locker)) { return; }  // Don't let  multiple threads in here at the same time.

        try
        {
            // do stuff
        }
        finally
        {
            Monitor.Exit(_locker);
        }
    }
3
Bob Horn

Vous pouvez arrêter le chronomètre avant la tâche et le redémarrer à la fin de la tâche. Cela vous permet d'effectuer votre travail périodiquement à intervalles réguliers.

public void myTimer_Elapsed(object sender, EventArgs e)
{
    myTimer.Stop();
    // Do something you want here.
    myTimer.Start();
}
3
Adil

Si je vous ai bien compris, vous voulez simplement vous assurer que votre thread n'est pas en cours d'exécution avant d'envoyer un autre thread. Disons que vous avez un fil défini dans votre classe comme si .

private System.Threading.Thread myThread;

Tu peux faire:

//inside some executed method
System.Threading.Timer t = new System.Threading.Timer(timerCallBackMethod, null, 0, 5000);

puis ajoutez le callBack comme si

private void timerCallBackMethod(object state)
{
     if(myThread.ThreadState == System.Threading.ThreadState.Stopped || myThread.ThreadState == System.Threading.ThreadState.Unstarted)
     {
        //dispatch new thread
     }
}
2
Chibueze Opata

Cela devrait faire ce que vous voulez. Il exécute un thread, puis le rejoint jusqu'à la fin. Va dans une boucle de minuterie pour s’assurer qu’il n’exécute pas un thread prématurément, puis se désactive et s’exécute.

using System.Threading;

public class MyThread
{
    public void ThreadFunc()
    {
        // do nothing apart from sleep a bit
        System.Console.WriteLine("In Timer Function!");
        Thread.Sleep(new TimeSpan(0, 0, 5));
    }
};

class Program
{
    static void Main(string[] args)
    {
        bool bExit = false;
        DateTime tmeLastExecuted;

        // while we don't have a condition to exit the thread loop
        while (!bExit)
        {
            // create a new instance of our thread class and ThreadStart paramter
            MyThread myThreadClass = new MyThread();
            Thread newThread = new Thread(new ThreadStart(myThreadClass.ThreadFunc));

            // just as well join the thread until it exits
            tmeLastExecuted = DateTime.Now; // update timing flag
            newThread.Start();
            newThread.Join();

            // when we are in the timing threshold to execute a new thread, we can exit
            // this loop
            System.Console.WriteLine("Sleeping for a bit!");

            // only allowed to execute a thread every 10 seconds minimum
            while (DateTime.Now - tmeLastExecuted < new TimeSpan(0, 0, 10));
            {
                Thread.Sleep(100); // sleep to make sure program has no tight loops
            }

            System.Console.WriteLine("Ok, going in for another thread creation!");
        }
    }
}

Devrait produire quelque chose comme:

Dans la fonction minuterie! Dormez pendant un moment! Ok. Entrez pour une autre création de fil! ! Dans la fonction de minuterie! .____...

J'espère que cela aide! SR

1
SolidRegardless

Le courage de ceci est la méthode ExecuteTaskCallback. Ce bit est chargé de faire du travail, mais seulement s'il ne le fait pas déjà. Pour cela, j'ai utilisé une ManualResetEvent (canExecute) initialement définie pour être signalée dans la méthode StartTaskCallbacks.

Notez la façon dont j'utilise canExecute.WaitOne(0). Le zéro signifie que WaitOne retournera immédiatement avec l'état de WaitHandle ( MSDN ). Si le zéro est omis, chaque appel à ExecuteTaskCallback finira par exécuter la tâche, ce qui pourrait être assez désastreux.

L'autre chose importante est de pouvoir terminer le traitement proprement. J'ai choisi d'éviter que Timer n'exécute d'autres méthodes dans StopTaskCallbacks car il semble préférable de le faire pendant que d'autres travaux sont en cours. Cela garantit qu'aucune nouvelle tâche ne sera entreprise et que l'appel ultérieur à canExecute.WaitOne(); couvrira effectivement la dernière tâche, le cas échéant.

private static void ExecuteTaskCallback(object state)
{
    ManualResetEvent canExecute = (ManualResetEvent)state;

    if (canExecute.WaitOne(0))
    {
        canExecute.Reset();
        Console.WriteLine("Doing some work...");
        //Simulate doing work.
        Thread.Sleep(3000);
        Console.WriteLine("...work completed");
        canExecute.Set();
    }
    else
    {
        Console.WriteLine("Returning as method is already running");
    }
}

private static void StartTaskCallbacks()
{
    ManualResetEvent canExecute = new ManualResetEvent(true),
        stopRunning = new ManualResetEvent(false);
    int interval = 1000;

    //Periodic invocations. Begins immediately.
    Timer timer = new Timer(ExecuteTaskCallback, canExecute, 0, interval);

    //Simulate being stopped.
    Timer stopTimer = new Timer(StopTaskCallbacks, new object[]
    {
        canExecute, stopRunning, timer
    }, 10000, Timeout.Infinite);

    stopRunning.WaitOne();

    //Clean up.
    timer.Dispose();
    stopTimer.Dispose();
}

private static void StopTaskCallbacks(object state)
{
    object[] stateArray = (object[])state;
    ManualResetEvent canExecute = (ManualResetEvent)stateArray[0];
    ManualResetEvent stopRunning = (ManualResetEvent)stateArray[1];
    Timer timer = (Timer)stateArray[2];

    //Stop the periodic invocations.
    timer.Change(Timeout.Infinite, Timeout.Infinite);

    Console.WriteLine("Waiting for existing work to complete");
    canExecute.WaitOne();
    stopRunning.Set();
}
1
nick_w

J'ai eu le même problème il y a quelque temps et tout ce que j'avais fait était d'utiliser l'instruction lock {} . Avec cela, même si le chronométreur veut faire quelque chose, il est obligé d'attendre jusqu'à la fin du blocage.

c'est à dire.

lock
{    
     // this code will never be interrupted or started again until it has finished
} 

C’est un excellent moyen d’être sûr que votre processus fonctionnera jusqu’à la fin sans interruption.

1
souichiro

Je recommande d'utiliser Timer au lieu de thread, car c'est un objet plus clair. Pour atteindre votre objectif, vous pouvez suivre les actions suivantes.

using System.Timers;

namespace sample_code_1
{
    public class ClassName
    {
        Timer myTimer;
        static volatile bool isRunning;

        public OnboardingTaskService()
        {
             myTimer= new Timer();
             myTimer.Interval = 60000;
             myTimer.Elapsed += myTimer_Elapsed;
             myTimer.Start();
        }

        private void myTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            if (isRunning) return;
            isRunning = true;
            try
            {
                //Your Code....
            }
            catch (Exception ex)
            {
                //Handle Exception
            }
            finally { isRunning = false; }
        }
    }
}

Faites-moi savoir si cela aide.

0
user1537674