How can I stop Device.StartTimer?

When I use System.Threading.Timer I can stop my timer and start it again:

protected override void OnScrollChanged(int l, int t, int oldl, int oldt)
{
    if (timer == null)
    {
        System.Threading.TimerCallback tcb = OnScrollFinished;
        timer = new System.Threading.Timer(tcb, null, 700, System.Threading.Timeout.Infinite);
    }
    else
    {
        timer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
        timer.Change(700, System.Threading.Timeout.Infinite);
    }
}

What is the best way to stop Device.StartTimer and start it again?

Tagged:

Best Answer

Answers

  • JohnHardmanJohnHardman GBUniversity mod

    @KirillRadchenko - To stop the timer resulting from calling Device.StartTimer, you simply return false from the callback method. As far as I am aware, there is no method to re-start a previously stopped timer, so you would simply call Device.StartTimer again.

    Regards,

    John H.

  • KirillRadchenkoKirillRadchenko RUMember ✭✭
    edited August 2015

    No, I need to stop timer in method OnScrollChanged.
    I can't stop it inside Device.StartTimer(...,()=>{OnScrollFinished(); return false;}) because timer will stop after OnScrollFinished method

  • KirillRadchenkoKirillRadchenko RUMember ✭✭

    I need to stop the timer before the method OnScrollFinished

  • adamkempadamkemp USInsider, Developer Group Leader mod

    You may be conflating two separate concepts: stopping the timer and canceling the timer. Stopping the timer is done by returning false from the callback, which obviously only stops subsequent invocations. Canceling would stop any already pending invocations. Device.StartTimer does not support cancelation at this time. That means you can't stop a pending timer from firing. You would have to set some state and have the timer callback look at that state before doing anything.

    The good news is that, unlike System.Threading.Timer, the Device.StartTimer callback is always called in the UI thread so you don't need any special synchronization on the state that you set. If you handle the scrolled event (in the UI thread) then you can set state and then check it in the callback (which will also be in the UI thread).

  • DerProgrammiererDerProgrammierer DEMember ✭✭✭
    edited January 2017

    I need to revive this old thread :#

    @adamkemp said:
    The good news is that, unlike System.Threading.Timer, the Device.StartTimer callback is always called in the UI thread so you don't need any special synchronization on the state that you set. If you handle the scrolled event (in the UI thread) then you can set state and then check it in the callback (which will also be in the UI thread).

    Do I have to call the Stop() method from the code which @ylemsoul posted also on the UI thread? It's not really something that is done in the UI, but I have a problem right now. The Stop() method works fine, but if I call it in the ViewModel by triggering the OnDisappearing event in my code behind file, it does not stop/cancel the timer.

    Code behind:

    protected override void OnDisappearing()
    {
        base.OnDisappearing();
        MessagingCenter.Send<MessagePhonePage>(this, "OnDisappearing");
    }
    

    ViewModel:

    MessagingCenter.Subscribe<MessagePhonePage>(this, "OnDisappearing", (sender) =>
    {
        if (StoppableTimer != null)
        {
            StoppableTimer.Stop();
        }
        MessagingCenter.Unsubscribe<MessagePhonePage>(this, "OnDisappearing");
    });
    

    StoppableTimer is exactly the same as @ylemsoul MyTimer class.

  • RobinTRobinT GBMember

    @DerProgrammierer said:
    I need to revive this old thread :#

    @adamkemp said:
    The good news is that, unlike System.Threading.Timer, the Device.StartTimer callback is always called in the UI thread so you don't need any special synchronization on the state that you set. If you handle the scrolled event (in the UI thread) then you can set state and then check it in the callback (which will also be in the UI thread).

    Do I have to call the Stop() method from the code which @ylemsoul posted also on the UI thread? It's not really something that is done in the UI, but I have a problem right now. The Stop() method works fine, but if I call it in the ViewModel by triggering the OnDisappearing event in my code behind file, it does not stop/cancel the timer.

    Code behind:

    protected override void OnDisappearing()
    {
        base.OnDisappearing();
        MessagingCenter.Send<MessagePhonePage>(this, "OnDisappearing");
    }
    

    ViewModel:

    MessagingCenter.Subscribe<MessagePhonePage>(this, "OnDisappearing", (sender) =>
    {
        if (StoppableTimer != null)
        {
            StoppableTimer.Stop();
        }
        MessagingCenter.Unsubscribe<MessagePhonePage>(this, "OnDisappearing");
    });
    

    StoppableTimer is exactly the same as @ylemsoul MyTimer class.

    I haven't tested this but I suspect you can just change:

    public void Stop()
    {
        Interlocked.Exchange(ref this.cancellation, new CancellationTokenSource()).Cancel();
    }
    

    to

    public void Stop()
    {
        CancellationTokenSource cts = new CancellationTokenSource();
        cts.Cancel();
        Interlocked.Exchange(ref this.cancellation, cts);
    }
    

    For reference MSDN defines that Interlocked.Exchange

    Sets an object to a specified value and returns a reference to the original object, as an atomic operation.

    So the call to Cancel() in the original code was unintentionally performed on the original, not the new token being exchanged.

  • adamkempadamkemp USInsider, Developer Group Leader mod

    This solution has the same problem that System.Threading.Timer has. If you are trying to stop a timer from a different thread than the timer fires on then there will always be a race condition because you could be trying to stop the timer after it has already begun firing. That means you cannot guarantee that you won't see side effects caused by the timer firing after calling Stop.

    The best way to handle this is to always stop a timer on the same thread that it fires. If you stick to that constraint (i.e., if you require that Stop is called only from the UI thread) then you shouldn't need an interlocked exchange at all. In fact, you don't need a cancellation token. You just need a simple boolean flag. When the timer fires it checks the flag before calling the callback. When Stop is called it just modifies the flag. Since these happen on the same thread there is no race condition, and no need for interlocked exchanges or other synchronization.

    Almost every usage of a multi-threaded timer API that I've ever seen has had race conditions that lead to subtle bugs. You really are better off just avoiding that problem altogether.

  • ylemsoulylemsoul RUMember ✭✭✭

    The solution solves original problem and there were no word about thread safety. Interlocked.Exchange may be confusing, but it just for convenience to make Stop with one line instead of three.

    Of course a simple boolean flag is not enough to handle restarts correctly. You need at least two flags. One - to track previous already scheduled callback and another to track the current. In the solution it was made with just one field and hidden field that is created with closure around cts variable. Now you have correct sliding time window when you scrolling. That's it.

  • adamkempadamkemp USInsider, Developer Group Leader mod

    There is no reason whatsoever to use Interlocked.Exchange unless you are trying to make something thread-safe. If Stop is only called from the UI thread then you don't need Interlocked.Exchange.

    Of course a simple boolean flag is not enough to handle restarts correctly.

    I didn't realize starting after stopping was a requirement. In that case you do need two flags (the second being "is there a pending timer"), but you still don't need Interlocked.Exchange, and you still don't need a CancellationTokenSource. This implementation would be sufficient for a single threaded use case:

    public class MyTimer
    {
        private readonly TimeSpan _timespan;
        private readonly Action _callback;
        private readonly bool _repeat;
    
        private bool _running;
        private bool _timerCallbackPending;
    
        public MyTimer(TimeSpan timespan, Action callback, bool repeat)
        {
            _timespan = timespan;
            _callback = callback;
            _repeat = repeat;
        }
    
        public void Start()
        {
            _running = true;
            ScheduleTimerIfNeeded();
        }
    
        public void Stop()
        {
            _running = false;
        }
    
        private void ScheduleTimerIfNeeded()
        {
            if (!_timerCallbackPending) {
            {
                _timerCallbackPending = true;
                Device.StartTimer(_timespan, TimerCallback);
            }
        }
    
        private bool TimerCallback()
        {
            if (_running)
            {
                _callback.Invoke();
            }
    
            bool reschedule = _repeat && _running;
    
            _timerCallbackPending = reschedule;
    
            return reschedule;
        }
    }
    

    Using Interlocked.Exchange in code that doesn't run on multiple threads is pointless and misleading.

    The only caveat is that if you call Stop() and then Start() then the timer may not start _timespan after the call to Start(), but instead it may fire when it would have fired had you never called Stop. If you need to stop a timer and then start another one that fires only after the full time has passed again then it makes more sense to stop this timer and then create a new one in its place.

  • ylemsoulylemsoul RUMember ✭✭✭

    No one says that you can solve it with the only one way. I just pointed out that the previous comment was misleading about its implementation. 50-lines solution instead of 30 is arguably easer to read if not harder.

  • adamkempadamkemp USInsider, Developer Group Leader mod

    I'm not saying it's the only way, but I am saying if you use Interlocked.Exchange for this you're doing something wrong. I don't think anyone should use that method for this use case.

  • SwathiSudarSwathiSudar USMember ✭✭
    edited June 2017


    Please help me how to figure it out
    ~~as per the link discussion
    https://github.com/ufuf/AdvancedTimer/issues/11

  • adamkempadamkemp USInsider, Developer Group Leader mod
    You are trying to use some third party component. This is not the right place to ask for help with that.
  • SwathiSudarSwathiSudar USMember ✭✭

    @adamkemp Sorry for that. I need Stopwatch with start, pause, stop.
    Please give suggestion to implement

  • HunumanHunuman GBMember ✭✭✭✭

    Hi @SwathiSudar

    AFAIK the Skia#.Forms namespace has a StopWatch.

    Hope this points you in the right direction,

    Tim

  • SwathiSudarSwathiSudar USMember ✭✭
    edited July 2017
    @Hunuman
    Actually I worked with stopwatch. It working fine but not visible the running time in the label. If I start button I can't able to see that ex: label is empty after I click pause button I see the value of pause ex:00:00:07:36 again I click start buttton it running on background but can't visible in the label it ex:00:00:07:36 after stop it. I can able to se the stop time like ex:00:0:20:67. I have code but please help me if anybody know the logics. I am doing xamarin.form

    using Java.Lang;
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;

    using Xamarin.Forms;
    using Xamarin.Forms.Xaml;

    namespace TimeSheet_Inhouse.Module
    {
       [XamlCompilation(XamlCompilationOptions.Compile)]
       public partial class TimerChrono : ContentPage
       {
         
          Label label;
           Stopwatch mStopWatch = new Stopwatch();
         
           public TimerChrono()
           {
               InitializeComponent();

               label = new Label()
               {
                   Text="00:00",

                   TextColor = Color.DarkBlue,
                   FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)),
                   VerticalOptions = LayoutOptions.Center,
                   HorizontalOptions = LayoutOptions.CenterAndExpand,
               };
               var but_start = new Button
               {
                   Text = "start",
                   BackgroundColor = Color.FromHex("#1A52BA"),
                   TextColor = Color.White,

                   BorderRadius = 20,
                   HorizontalOptions = LayoutOptions.Center

               };

               var but_stop = new Button
               {
                   Text = "stop",
                   BackgroundColor = Color.FromHex("#1A52BA"),
                   TextColor = Color.White,

                   BorderRadius = 20,
                   HorizontalOptions = LayoutOptions.Center

               };

               var but_pause = new Button
               {
                   Text = "pause",
                   BackgroundColor = Color.FromHex("#1A52BA"),
                   TextColor = Color.White,

                   BorderRadius = 20,
                   HorizontalOptions = LayoutOptions.Center
               };

               
               but_start.Clicked += (object sender, EventArgs e) =>
               {
                   mStopWatch.Start();
                   var a = mStopWatch.Elapsed;
                   System.Diagnostics.Debug.WriteLine("***************************************" + a + "************************");
                   label.Text = string.Format("{0:hh\\:mm\\:ss}", a);
                   //Boolean Value = true;
                   //Device.StartTimer(new TimeSpan(0, 0, 1), () =>{
                   //    if (Value)
                   //    {
                   //        mStopWatch.Start();
                   //        var a = mStopWatch.Elapsed;
                   //        System.Diagnostics.Debug.WriteLine("***************************************" + a + "************************");
                   //        label.Text = string.Format("{0:hh\\:mm\\:ss}", a);
                   //        return true;
                   //    }
                   //    return false;
                   //});
               };
               
               but_pause.Clicked += (object sender, EventArgs e) =>
               {
                   mStopWatch.Stop();
                   but_start.Text = "Restart";
                   var a = mStopWatch.Elapsed;
  • RCarvalhoRCarvalho PTMember ✭✭

    I implemented one simple timer as follows:

    MainViewModel.cs

    public class MainViewModel : ViewModelBase
        {
            private Timer _timer;
    
            private TimeSpan _totalSeconds = new TimeSpan(0,0,0,10);
            public TimeSpan TotalSeconds
            {
                get { return _totalSeconds; }
                set { Set(ref _totalSeconds, value); }
            }
    
            public Command StartCommand { get; set; }
            public Command PauseCommand { get; set; }
            public Command StopCommand { get; set; }
    
            /// <summary>
            /// Initializes a new instance of the MainViewModel class.
            /// </summary>
            public MainViewModel()
            {
                StartCommand = new Command(StartTimerCommand);
                PauseCommand = new Command(PauseTimerCommand);
                StopCommand = new Command(StopTimerCommand);
                //make sure you put this in the constructor
                _timer = new Timer(TimeSpan.FromSeconds(1), CountDown);
                TotalSeconds = _totalSeconds;
            }
    
            private void StartTimerCommand()
            {
                _timer.Start();
            }
    
            /// <summary>
            /// Counts down the timer
            /// </summary>
            private void CountDown()
            {
                if (_totalSeconds.TotalSeconds == 0)
                {
                    //do something after hitting 0, in this example it just stops/resets the timer
                    StopTimerCommand();               
                }
                else
                {
                    TotalSeconds = _totalSeconds.Subtract(new TimeSpan(0,0,0,1));
                }           
            }
    
            /// <summary>
            /// Pauses the timer
            /// </summary>
            private void PauseTimerCommand()
            {
                _timer.Stop();
            }
    
            /// <summary>
            /// Stops and resets the timer
            /// </summary>
            private void StopTimerCommand()
            {
                TotalSeconds = new TimeSpan(0,0,0,10);
                _timer.Stop();
            }
        } 
    

    Timer.cs

    public class Timer
        {
            private readonly TimeSpan _timeSpan;
            private readonly Action _callback;
    
            private static CancellationTokenSource _cancellationTokenSource;
    
            public Timer(TimeSpan timeSpan, Action callback)
            {
                _timeSpan = timeSpan;
                _callback = callback;
                _cancellationTokenSource = new CancellationTokenSource();
            }
            public void Start()
            {
                CancellationTokenSource cts = _cancellationTokenSource; // safe copy
                Device.StartTimer(_timeSpan, () =>
                {
                    if (cts.IsCancellationRequested)
                    {
                        return false;
                    }
                    _callback.Invoke();
                    return true; //true to continuous, false to single use
                });
            }
    
            public void Stop()
            {
                Interlocked.Exchange(ref _cancellationTokenSource, new CancellationTokenSource()).Cancel();
            }
        }
    

    MainPage.xaml

    <?xml version="1.0" encoding="utf-8" ?>
    <ContentPage 
                 xmlns:local="clr-namespace:SimpleTimer"
                 xmlns:viewModel="clr-namespace:SimpleTimer.ViewModel;assembly=SimpleTimer"
                 x:Class="SimpleTimer.Views.MainPage"
                 Title="SimpleTimer">
        <ContentPage.BindingContext>
            <viewModel:MainViewModel></viewModel:MainViewModel>
        </ContentPage.BindingContext>
        <ContentPage.Content>
            <StackLayout Orientation="Vertical">
                <Label Text="{Binding TotalSeconds, StringFormat='{0:mm\\:ss}'}" HorizontalOptions="CenterAndExpand" FontAttributes="Bold" FontSize="20" TextColor="Black"></Label>
                <Button Text="Start" Command="{Binding StartCommand}" FontSize="20"></Button>
                <Button Text="Pause" Command="{Binding PauseCommand}" FontSize="20"></Button>
                <Button Text="Stop" Command="{Binding StopCommand}" FontSize="20"></Button>
            </StackLayout>
        </ContentPage.Content>
    </ContentPage>
    

    I hope this helps out anyone. Cheers

  • DannykhreetDannykhreet USMember ✭✭

    @RCarvalho said:
    I implemented one simple timer as follows:

    MainViewModel.cs

    public class MainViewModel : ViewModelBase
        {
            private Timer _timer;
    
            private TimeSpan _totalSeconds = new TimeSpan(0,0,0,10);
            public TimeSpan TotalSeconds
            {
                get { return _totalSeconds; }
                set { Set(ref _totalSeconds, value); }
            }
    
            public Command StartCommand { get; set; }
            public Command PauseCommand { get; set; }
            public Command StopCommand { get; set; }
    
            /// <summary>
            /// Initializes a new instance of the MainViewModel class.
            /// </summary>
            public MainViewModel()
            {
                StartCommand = new Command(StartTimerCommand);
                PauseCommand = new Command(PauseTimerCommand);
                StopCommand = new Command(StopTimerCommand);
                //make sure you put this in the constructor
                _timer = new Timer(TimeSpan.FromSeconds(1), CountDown);
                TotalSeconds = _totalSeconds;
            }
    
            private void StartTimerCommand()
            {
                _timer.Start();
            }
    
            /// <summary>
            /// Counts down the timer
            /// </summary>
            private void CountDown()
            {
                if (_totalSeconds.TotalSeconds == 0)
                {
                    //do something after hitting 0, in this example it just stops/resets the timer
                    StopTimerCommand();               
                }
                else
                {
                    TotalSeconds = _totalSeconds.Subtract(new TimeSpan(0,0,0,1));
                }           
            }
    
            /// <summary>
            /// Pauses the timer
            /// </summary>
            private void PauseTimerCommand()
            {
                _timer.Stop();
            }
    
            /// <summary>
            /// Stops and resets the timer
            /// </summary>
            private void StopTimerCommand()
            {
                TotalSeconds = new TimeSpan(0,0,0,10);
                _timer.Stop();
            }
        } 
    

    Timer.cs

    public class Timer
        {
            private readonly TimeSpan _timeSpan;
            private readonly Action _callback;
    
            private static CancellationTokenSource _cancellationTokenSource;
    
            public Timer(TimeSpan timeSpan, Action callback)
            {
                _timeSpan = timeSpan;
                _callback = callback;
                _cancellationTokenSource = new CancellationTokenSource();
            }
            public void Start()
            {
                CancellationTokenSource cts = _cancellationTokenSource; // safe copy
                Device.StartTimer(_timeSpan, () =>
                {
                    if (cts.IsCancellationRequested)
                    {
                        return false;
                    }
                    _callback.Invoke();
                    return true; //true to continuous, false to single use
                });
            }
    
            public void Stop()
            {
                Interlocked.Exchange(ref _cancellationTokenSource, new CancellationTokenSource()).Cancel();
            }
        }
    

    MainPage.xaml

    <?xml version="1.0" encoding="utf-8" ?>
    <ContentPage 
                 xmlns:local="clr-namespace:SimpleTimer"
                 xmlns:viewModel="clr-namespace:SimpleTimer.ViewModel;assembly=SimpleTimer"
                 x:Class="SimpleTimer.Views.MainPage"
                 Title="SimpleTimer">
        <ContentPage.BindingContext>
            <viewModel:MainViewModel></viewModel:MainViewModel>
        </ContentPage.BindingContext>
        <ContentPage.Content>
            <StackLayout Orientation="Vertical">
                <Label Text="{Binding TotalSeconds, StringFormat='{0:mm\\:ss}'}" HorizontalOptions="CenterAndExpand" FontAttributes="Bold" FontSize="20" TextColor="Black"></Label>
                <Button Text="Start" Command="{Binding StartCommand}" FontSize="20"></Button>
                <Button Text="Pause" Command="{Binding PauseCommand}" FontSize="20"></Button>
                <Button Text="Stop" Command="{Binding StopCommand}" FontSize="20"></Button>
            </StackLayout>
        </ContentPage.Content>
    </ContentPage>
    

    I hope this helps out anyone. Cheers

    what is this method
    Set(ref _totalSeconds, value);

    thanks

  • DeepakDYDeepakDY INMember ✭✭✭
    edited May 30

    This code work for me

    Device.StartTimer(TimeSpan.FromMinutes(2), () =>
    {
        if (true)
        {
            return 
        }
        return false; 
    });
    

    if Device.StartTimer return false then it will stop

    and follow this link for more detail

    https://stackoverflow.com/questions/32304766/how-can-i-cancel-from-device-starttimer

Sign In or Register to comment.