便携式图书馆中的计时器

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 包中。

【讨论】:

以上是关于便携式图书馆中的计时器的主要内容,如果未能解决你的问题,请参考以下文章

Application.DoEvents()的使用

PHP 多任务秒级定时器的实现方法

Tableview Swift 中的多个计时器

TCP中的计时器

javascript中的普通倒计时计时器

MVC 中的倒数计时器