如何在定时器完成前取消它

6

我正在开发一款聊天应用程序。当聊天消息被加载并可见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())?
3个回答

11

我在 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(),因此不应在 Forms 项目中使用。只需使用 Device.StartTimer() 即可。 - BrewMate
@BrewMate 为什么在 Forms 项目中不应该使用这个?我没有使用 System.Threading.Timer,但是使用这段代码完全没有问题。 - Dennis Schröer

3
你可以尝试使用我找到的这个类,它覆盖了 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();

2

使用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;
}

1
根据Xamarin文档,“当回调返回true时,计时器将继续循环。” - 这意味着计时器在完成后会重新启动。这不是我想要实现的。 - Dennis Schröer
此外,您的代码将始终调用 SendReadConfirmationAsync,如果在计时器运行时再次调用 RefreshLocalData,则不应如此。我认为您误解了我的问题。 - Dennis Schröer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接