【问题标题】: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 映射:-(
  • 这可能对你有帮助:stackoverflow.com/a/14945407/122781

标签: c# timer microsoft-metro portable-class-library


【解决方案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 包中。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-07-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-12-30
        • 2011-07-22
        • 1970-01-01
        相关资源
        最近更新 更多