这是我想出的代码。它似乎有效,但我担心这可能不是做我想做的事情的好方法。 我需要的是每分钟在出现时立即运行一个方法,并让它随着 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();
}
不是问题的一部分,但我也欢迎对代码的任何评论。
从 Device.StartTimer
返回正确的值来简化此操作,以重复true
,不重复false
,也不使用StopWatch
。(源声明While the callback returns true, the timer will keep recurring.
正如您从源中看到的,该方法不需要Func<Task<bool>>
它只需要一个Func<bool>
回调,因此不需要使用Task
。
在课堂上
volatile bool run;
在出现时
run = true;
Device.StartTimer(new TimeSpan(0, 1, 0), () => {
if (run) { /*do what you want;*/ return true; }
else { return false; }
});
in 消失
run = false;
<小时 />编辑 - 根据OP的要求
这是代码。我留下我原来的答案来帮助其他需要这个的人。
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();
}
您可以重构代码以正确使用计时器与CancellationToken
结合使用。
另请注意,使用 async 事件处理程序来避免火灾并忘记对不允许捕获引发的异常并可能导致崩溃的async void OnAppearing
调用。
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();
}
取消令牌将用于强制计时器返回false
并在令牌在OnDisappearing()
中取消时停止重复。
如果要重复的函数需要异步,请添加另一个异步事件处理程序来管理该函数。
你可以使用 Rx.Timer 来实现这一点:
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
...
}
尝试创建一个负责执行定时检查的新类。
使用布尔属性继续检查。它执行睡眠,然后调用不同的方法来执行工作,然后检查是否必须继续检查。如果是这样,它就会调用自己。
在 onT消失 中,您将 continueCheck 设置为 false,因此该方法停止调用自身。
通过这样做,您将开始和结束循环的关注点、循环本身和它必须做的工作分散在三个不同的地方。
编辑:代码示例
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
}
}