便携式图书馆中的计时器
Posted
技术标签:
【中文标题】便携式图书馆中的计时器【英文标题】:Timer in Portable Library 【发布时间】:2012-09-15 07:45:58 【问题描述】:我在便携式库/Windows 应用商店中找不到计时器。 (针对 .net 4.5 和 Windows Store aka Metro)
有没有人知道如何创建某种计时事件?
我需要一个秒表,所以它应该每秒刷新一次左右
【问题讨论】:
嗯 - 我已经创建了一个明确的 Windows 应用商店库,而且确实没有 System.Threading.Timer。这很奇怪,因为当我制作了一个同样针对 Windows 应用商店的可移植类库时,它就起作用了。听起来像是 PCL 的“支持什么”元数据中的错误。 我认为问题在于 Metro 中的计时器与 RT 中的计时器不同。因为在 RT 中我只能找到一个 Dispatcher 计时器。所以这不能通过 PCL 映射:-( 这可能对你有帮助:***.com/a/14945407/122781 【参考方案1】:更新:我们已在 Visual Studio 2013 中修复此问题。面向 Store (Windows 8.1) 和 .NET Framework 4.5.1 项目的可移植库现在可以引用 Timer。
这是我们的实现细节泄露给用户的不幸案例。当您仅针对 .NET 4.5 和 Windows Store 应用程序时,我们实际上会让您针对某些不同的东西进行构建,而不是针对低级平台(.NET 4、SL 4/5、Phone 7.x)。我们尝试将这两个视为相同,但下面的有限更改开始泄漏(例如 Timer 和 Reflection)。我们在这里介绍了其中的一些内容:http://channel9.msdn.com/Shows/Going+Deep/NET-45-David-Kean-and-Marcea-Trofin-Portable-Libraries。
我们将在未来的版本中解决此问题。在此之前,您有几个解决方法:
1) 使用 Task.Delay 实现您自己的 Timer 版本,这是我们内部使用的快速副本:
internal delegate void TimerCallback(object state);
internal sealed class Timer : CancellationTokenSource, IDisposable
internal Timer(TimerCallback callback, object state, int dueTime, int period)
Contract.Assert(period == -1, "This stub implementation only supports dueTime.");
Task.Delay(dueTime, Token).ContinueWith((t, s) =>
var tuple = (Tuple<TimerCallback, object>)s;
tuple.Item1(tuple.Item2);
, Tuple.Create(callback, state), CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.Default);
public new void Dispose() base.Cancel();
2) 将您的项目降级到 .NET 4.0 和 Windows Store 应用程序,这样您就可以访问 Timer。
3) 创建一个面向 .NET 4.0 和 Windows Store 应用程序的新项目,并将需要计时器的代码放入其中。然后从 .NET 4.5 和 Windows 应用商店应用项目中引用它。
作为旁注,我已经在 PclContrib 网站上为自己提交了一个工作项以添加计时器支持:http://pclcontrib.codeplex.com/workitem/12513。
【讨论】:
另一条评论,我们发布了一个更新的 Async 包,为针对低级平台的可移植库添加了 await/async 支持:blogs.msdn.com/b/bclteam/archive/2012/10/22/… 预发布的异步包看起来不错,除了目标 4.0 似乎失去了 ObservableCollection 所以我们回到 PCL 跳跃... 看来这还是很突出的。我们有修复的预计到达时间吗? @Rui,我们只在面向“.NET 4.5.1, Windows 8.1 & Windows Phone 8.1”时支持Timer @DavidKean 我经常使用 System.Threading.Timer's Change(int dueTime, int period),但在使用 Profile 78 的 Xamarin 上,我无法使用 System.Threading.Timer,因此我现在使用你的定时器实现,谢谢!你知道如何在你的 Timer 中实现 Change(int dueTime, int period) 吗?【参考方案2】:根据 David Kean 的建议 #3,这是我的 hacky Timer 适配器 - 将其放入针对 .net 4.0 的 PCL 库中,并从 4.5 引用它:
public class PCLTimer
private Timer _timer;
private Action _action;
public PCLTimer(Action action, TimeSpan dueTime, TimeSpan period)
_action = action;
_timer = new Timer(PCLTimerCallback, null, dueTime, period);
private void PCLTimerCallback(object state)
_action.Invoke();
public bool Change(TimeSpan dueTime, TimeSpan period)
return _timer.Change(dueTime, period);
然后要使用它,您可以从 4.5 PCL 库中执行此操作:
private void TimeEvent()
//place your timer callback code here
public void SetupTimer()
//set up timer to run every second
PCLTimer _pageTimer = new PCLTimer(new Action(TimeEvent), TimeSpan.FromMilliseconds(-1), TimeSpan.FromSeconds(1));
//timer starts one second from now
_pageTimer.Change(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1));
【讨论】:
弥合这一差距的好主意。但是,我注意到您在构造函数中忽略了 dueTime 和 period。 我实现了这个解决方案,它工作正常。但现在我有警告: Xamarin.android.Common.targets(3,3): 警告 MSB3247: 发现同一依赖程序集的不同版本之间存在冲突。 (MSB3247) (Prototype.Droid) 如何摆脱它? 变革的实施在哪里?【参考方案3】:David Kean 的建议 #1 的实施:
public delegate void TimerCallback(object state);
public sealed class Timer : CancellationTokenSource, IDisposable
public Timer(TimerCallback callback, object state, int dueTime, int period)
Task.Delay(dueTime, Token).ContinueWith(async (t, s) =>
var tuple = (Tuple<TimerCallback, object>) s;
while (true)
if (IsCancellationRequested)
break;
Task.Run(() => tuple.Item1(tuple.Item2));
await Task.Delay(period);
, Tuple.Create(callback, state), CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.Default);
public new void Dispose() base.Cancel();
【讨论】:
【参考方案4】:我通过包含一个新参数改进了 Ivan Leonenko 的回答,如果时间段小于回调运行时间,该参数会排队调用您的回调。并用一个动作替换了旧的 TimerCallback。最后,在最后的延迟中使用我们的取消标记,并使用 ConfigureAwait 来增加并发,因为回调可以在任何线程上执行。
internal sealed class Timer : CancellationTokenSource
internal Timer(Action<object> callback, object state, int millisecondsDueTime, int millisecondsPeriod, bool waitForCallbackBeforeNextPeriod = false)
//Contract.Assert(period == -1, "This stub implementation only supports dueTime.");
Task.Delay(millisecondsDueTime, Token).ContinueWith(async (t, s) =>
var tuple = (Tuple<Action<object>, object>) s;
while (!IsCancellationRequested)
if (waitForCallbackBeforeNextPeriod)
tuple.Item1(tuple.Item2);
else
Task.Run(() => tuple.Item1(tuple.Item2));
await Task.Delay(millisecondsPeriod, Token).ConfigureAwait(false);
, Tuple.Create(callback, state), CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default);
protected override void Dispose(bool disposing)
if(disposing)
Cancel();
base.Dispose(disposing);
【讨论】:
这很有帮助,尽管需要做一些工作来处理毫秒周期为负数的情况,即需要运行一次。 另外,如果 Task.Run(() => tuple.Item1(tuple.Item2)); 你将如何捕捉错误失败了? 在你的回调中添加一个 try / catch。捕捉错误不是计时器的责任。一次运行我让你添加代码,太容易了。【参考方案5】:您可以使用 PCL 库创建计时器接口,然后使用 W8S 计时器在第二个 W8S 库中创建该接口的实现。
然后您可以使用依赖注入将 W8S 库注入 PCL 类。
【讨论】:
【参考方案6】:我最终得到了来自 Reactive Extensions (Rx) 的 Observable.Timer
。 Rx 已经包含在项目中,所以额外的参考不是问题。
这是一个每秒触发一次的计时器:
IDisposable timer = Observable.Timer(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1))
.Subscribe(_ => /* your useful code here */);
// unsubscribe/stop when timer is no longer needed
timer.Dispose();
System.Reactive.Linq.Observable
类在 PCL 友好的Rx-Linq
NuGet 包中。
【讨论】:
以上是关于便携式图书馆中的计时器的主要内容,如果未能解决你的问题,请参考以下文章