3
votes

Je veux exécuter une méthode chaque minute qui démarre à partir de mon OnAppearing (). Dois-je l'exécuter en tant que tâche?

Voici le code que j'ai trouvé. Cela semble fonctionner, mais je crains que ce ne soit pas une bonne façon de faire ce que je veux faire. Ce dont j'ai besoin, c'est d'exécuter une méthode toutes les minutes dès que l'OnAppearing se produit et de l'arrêter avec OnDisappearing ();

protected async override void OnAppearing()
{
   base.OnAppearing();
   BindingContext = vm;

   cts = new CancellationTokenSource();
   if (Settings.mode == MO.Practice)
   {
      if (!App.stopWatch.IsRunning) { App.stopWatch.Start(); }
            Device.StartTimer(new TimeSpan(0, 0, 5), () =>
      {
          if (App.stopWatch.IsRunning && App.stopWatch.Elapsed.Seconds >= 60)
          {
             // Here's the method I want to run. After it's finished
             // I call BeginInvoke .. to update info on the screen
             if (App.DB.ReducePoints() == true)
                Device.BeginInvokeOnMainThread(() =>
                {
                   vm.PifInfo = GetPifInfo();
                });
                App.stopWatch.Restart();
             }
             return true;
            });
        }
        await GetCards(cts.Token);
    }
}

protected override void OnDisappearing()
{
    Unsubscribe();
    cts.Cancel();
    if (App.stopWatch.IsRunning) { App.stopWatch.Stop(); }
    base.OnDisappearing();
}

Ne fait pas partie de la question, mais je serais ravi de tout commentaire code aussi. Merci


1 commentaires

Déplacez la logique dans une autre classe et appelle NewClass.StartTimer () dans OnAppearing et NewClass.StopTimer () dans OnDisappearing ()


4 Réponses :


0
votes

essayez de créer une nouvelle classe responsable de la vérification chronométrée.

avec une propriété booléenne continueChecking. Il fait un sommeil, puis appelle une méthode différente pour faire le travail, puis vérifie s'il doit continuer à vérifier. si c'est le cas, il s'appelle lui-même.

Dans onDisappearing, vous définissez continueChecking sur false, la méthode arrête de s'appeler elle-même.

En faisant cela, vous divisez le souci de démarrer et de terminer la boucle, la boucle elle-même et le travail qu'elle doit faire en trois endroits distincts.

Edit: exemple de code

public class ButtonClicked
    {
        //make sure you've got the same instance on both methods
        private Loop loop = new Loop();

        protected async void OnAppearing()
        {
            //other work
            await loop.StartLoop();
        }

        protected void OnDisappearing()
        {
            //other work
            loop.StopLoop();
        }
    }

    public class Loop
    {
        private bool _continueChecking;
        private readonly TimeSpan interval = new TimeSpan(0,1,0);

        public async Task StartLoop()
        {
            await DoLoop();
        }
        public void StopLoop()
        {
            _continueChecking = false;
        }

        private async Task DoLoop()
        {
            _continueChecking = true;
            await Task.Factory.StartNew(() =>
            {
                System.Threading.Thread.Sleep(interval);
                TheWork();
            });
            if (_continueChecking)
            {
                DoLoop();
            }
        }


        private void TheWork()
        {
           //specific work stuff
           //can be anywhere so it is testable and reusable
        }
    }


0 commentaires

6
votes

Vous pouvez le faire plus simplement en renvoyant la valeur correcte de Device.StartTimer , pour répéter true , pour ne pas répéter false et ne pas utiliser un Chronomètre . ( source indique que Tant que le rappel retourne true, le minuteur continuera de se répéter. Et comme vous le voyez à partir de la source, la méthode n'a pas besoin d'un Func> il n'a besoin que d'un rappel Func donc il n'est pas nécessaire d'utiliser une Task.)

dans la classe p >

volatile bool run;

protected async override void OnAppearing()
{
   base.OnAppearing();
   BindingContext = vm;    
   cts = new CancellationTokenSource();
   if (Settings.mode == MO.Practice)
   {
        run = true;
        Device.StartTimer(new TimeSpan(0, 1, 0), () => 
        {
            if (run) 
            { 
                 if (App.DB.ReducePoints() == true)
                    Device.BeginInvokeOnMainThread(() =>
                    {
                       vm.PifInfo = GetPifInfo();
                    });
                 return true; 
            }
            else { return false; }
        });
        await GetCards(cts.Token);
    }
}

protected override void OnDisappearing()
{
    run = false;
    Unsubscribe();
    cts.Cancel();
    base.OnDisappearing();
}

dans OnAppearing

run = false;

dans OnDisappearing

XXX


EDIT - comme demandé par OP

Voici le code. Je laisse ma réponse originale pour aider toute autre personne qui en a besoin.

run = true;
Device.StartTimer(new TimeSpan(0, 1, 0), () => {
if (run) { /*do what you want;*/ return true; }
else { return false; }
});


4 commentaires

Pouvez-vous donner un exemple complet en utilisant tout le code de la question pour que les gens puissent voir plus facilement ce que vous voulez dire. Merci


@ Alan2 Oui. Je l'ai fait.


@ Alan2 pour mémoire, je vous suggère d'éviter async void sur les gestionnaires non événementiels qui peuvent déstabiliser votre application.


@ Alan2 J'ai ajouté quelques informations à mon premier paragraphe.



3
votes

Vous pouvez utiliser Rx.Timer pour cela:

protected async override void OnAppearing()
{
   _sub = Observable.Timer(0, TimeSpan.FromMinutes(1))
           .ObserveOnDispatcher() // move invocation to dispatcher
           .Do(_ => {
                  vm.PifInfo = GetPifInfo(); // do your work. Try/catch will be usefull, otherwise any exception will break the subscription
            })
       //    .Retry() // uncomment this if you want to immedietally try again after a failure, no try/catch then
           .Subscribe();

    ....

}

protected override void OnDisappearing()
{
   _sub?.Dispose(); // this will unsubscribe to the timer
   ...
}


0 commentaires

3
votes

Vous pouvez refactoriser le code pour utiliser correctement le Timer en combinaison avec un CancellationToken .

Notez également l'utilisation du gestionnaire d'événements async pour éviter l'incendie et oublier l'appel à async void OnAppearing qui ne permet pas d'intercepter les exceptions levées et peut provoquer des plantages.

CancellationTokenSource source;

protected override void OnAppearing() {
    base.OnAppearing();
    BindingContext = vm;
    timerStarted += onTimerStarted;
    timerStarted(this, EventArgs.Empty);
}

private event EventHandler timerStarted = delegate { };

private async void onTimerStarted(object sender, EventArgs args) {
    timerStarted -= onTimerStarted;
    cts = new CancellationTokenSource();
    if (Settings.mode == MO.Practice) {
        source = new CancellationTokenSource();
        StartTimer(source.Token);
        await GetCards(cts.Token);
    }
}

private void StartTimer(CancellationToken token) {        
    var interval = TimeSpan.FromMinutes(1);
    Func<bool> callback = () => {
        //check if to stop timer
        if(token.IsCancellationRequested) return false;
        //Code to be repeated
        checkPoints();            
        //While the callback returns true, the timer will keep recurring.
        return true;
    };
    //repeat this function every minute
    Device.StartTimer(interval, callback);
}

private void checkPoints() {
    // Here's the method I want to run. After it's finished
    // I call BeginInvoke .. to update info on the screen
    if (App.DB.ReducePoints() == true) {
        Device.BeginInvokeOnMainThread(() => {
            vm.PifInfo = GetPifInfo();
        });
    }
}

protected override void OnDisappearing() {        
    source.Cancel();//Timer will short-circuit on next interval
    Unsubscribe();
    cts.Cancel();        
    base.OnDisappearing();
}

Le jeton d'annulation sera utilisé pour forcer le minuteur à renvoyer false et arrêter de se reproduire lorsque le jeton est annulé dans OnDisappearing () .

Si la fonction à répéter doit être asynchrone, ajoutez un autre gestionnaire d'événements asynchrones pour gérer cela.


0 commentaires