【问题标题】:How to cancel a Timer before it's finished如何在完成之前取消计时器
【发布时间】:2017-01-11 09:01:29
【问题描述】:

我正在开发一个聊天应用程序。加载聊天消息并且消息可见 5 秒钟后,我想向服务器发送读取确认。到目前为止,这是我想出的:

public async void RefreshLocalData()
{
    // some async code to load the messages

    if (_selectedChat.countNewMessages > 0)
    {
        Device.StartTimer(TimeSpan.FromSeconds(5), SendReadConfirmation);
    }
}

RefreshLocalData() 被调用时,我知道用户选择了另一个聊天或当前聊天收到了新消息。所以当RefreshLocalData()被调用时,我必须取消当前的定时器来开始一个新的。

我必须取消计时器的另一种情况是当我导航到另一个Page 时。这没问题,因为发生这种情况时,整个ViewModel 都会被释放。

使用上面的代码,如果再次调用RefreshLocalData(),但声明的5秒TimeSpan还没有结束,则该方法仍在执行中。

有没有办法取消定时器(如果再次调用RefreshLocalData())?

【问题讨论】:

    标签: xamarin timer xamarin.forms


    【解决方案1】:

    我在 Xamarin 论坛中找到了这个答案:https://forums.xamarin.com/discussion/comment/149877/#Comment_149877

    我已对其进行了一些更改以满足我的需求,并且此解决方案正在运行:

    public class StoppableTimer
    {
        private readonly TimeSpan timespan;
        private readonly Action callback;
    
        private CancellationTokenSource cancellation;
    
        public StoppableTimer(TimeSpan timespan, Action callback)
        {
            this.timespan = timespan;
            this.callback = callback;
            this.cancellation = new CancellationTokenSource();
        }
    
        public void Start()
        {
            CancellationTokenSource cts = this.cancellation; // safe copy
            Device.StartTimer(this.timespan,
                () => {
                    if (cts.IsCancellationRequested) return false;
                    this.callback.Invoke();
                    return false; // or true for periodic behavior
            });
        }
    
        public void Stop()
        {
            Interlocked.Exchange(ref this.cancellation, new CancellationTokenSource()).Cancel();
        }
    
        public void Dispose()
        {
    
        }
    }
    

    这就是我在RefreshLocalData() 方法中使用它的方式:

    private StoppableTimer stoppableTimer;
    
    public async void RefreshLocalData()
    {
        if (stoppableTimer != null)
        {
            stoppableTimer.Stop();
        }
    
        // some async code to load the messages
    
        if (_selectedChat.countNewMessages > 0)
        {
            if (stoppableTimer == null)
            {
                stoppableTimer = new StoppableTimer(TimeSpan.FromSeconds(5), SendReadConfirmation);
                stoppableTimer.Start();
            }
            else
            {
                stoppableTimer.Start();
            }
        }
    }
    

    【讨论】:

    • 这是为了使用System.Threading.Timer,而不是为了Xamarin.Forms.Device.StartTimer(),并且不应在表单项目中使用。只需使用Device.StartTimer()
    • @BrewMate 为什么不应该在 Forms 项目中使用它?我没有使用System.Threading.Timer,它在这段代码中运行良好
    【解决方案2】:

    你可以尝试使用我找到的这个类,它涵盖了 DeviceTimer 的一些限制:

    public class MySystemDeviceTimer
    {
        private readonly TimeSpan timespan;
        private readonly Action callback;
    
        private CancellationTokenSource cancellation;
    
        public bool running { get; private set; }
    
        public MySystemDeviceTimer(TimeSpan timespan, Action callback)
        {
            this.timespan = timespan;
            this.callback = callback;
            this.cancellation = new CancellationTokenSource();
        }
    
        public void Start()
        {
            running = true;
            start(true);
        }
    
        private void start(bool continuous)
        {
            CancellationTokenSource cts = this.cancellation;    // safe copy
            Device.StartTimer(this.timespan,
                () =>
                {
                    if (cts.IsCancellationRequested)
                    {
                        running = false;
                        return false;
                    }
                    this.callback.Invoke();
                    return continuous;
                });
        }
    
        public void FireOnce()
        {
            running = true;
            start(false);
            running = false;
        }
    
        public void Stop()
        {
            Interlocked.Exchange(ref this.cancellation, new CancellationTokenSource()).Cancel();
        }
    }
    

    那么为了你的目的:

    MySystemDeviceTimer 定时器;

        if (timer == null)
        {
           timer = new MySystemDeviceTimer(TimeSpan.FromSeconds(5), SendReadConfirmation);
           timer.FireOnce();
        }
        else if (timer.running)
           timer.Stop();
    

    【讨论】:

      【解决方案3】:

      是的,您可以使用 Device.StartTimer(),只要您返回 true 以重复该功能。我通常通过一个可以在 ViewModel 中控制的布尔变量来处理这个问题。如下所示:

      bool shouldRun = true;
      public async void RefreshLocalData()
      {
          // some async code to load the messages
      
          if (_selectedChat.countNewMessages > 0)
          {
              Device.StartTimer(TimeSpan.FromSeconds(5), async() => 
              {
                  await SendReadConfirmationAsync()
                  return shouldRun;
              });
      
          }
      }
      
      public async Task SendReadConfirmationAsync()
      {
          //Do some stuff
          if(we want to stop call)
              shouldRun = false;
      }
      

      【讨论】:

      • 根据 Xamarin 文档,“当回调返回 true 时,计时器将继续重复。” - 这意味着计时器将在完成后重新启动。这不是我想要达到的目标
      • 此外,您的代码将始终调用SendReadConfirmationAsync,如果在计时器运行时再次调用RefreshLocalData,则不应出现这种情况。我想你误解了我的问题
      猜你喜欢
      • 2014-10-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-02-06
      相关资源
      最近更新 更多