便携式库中的定时器

33

我在可移植库 / Windows Store 中找不到计时器。(目标为 .net 4.5 和 Windows Store,也称为 Metro)

有没有人知道如何创建某种定时事件?

我需要一种类似秒表的东西,所以这应该每秒钟或者这样刷新一次。


2
嗯 - 我创建了一个显式的Windows Store库,确实没有System.Threading.Timer。这很奇怪,因为当我创建一个也针对Windows Store的可移植类库时,它可以工作。听起来像是PCL的“支持什么”的元数据中出现了错误。 - Marc Gravell
1
我认为问题在于Metro中的计时器与RT中的计时器不相似。因为在RT中,我只能找到一个Dispatcher计时器。所以这不能通过PCL映射 :-( - Boas Enkler
2
这可能会对您有所帮助:https://dev59.com/lG445IYBdhLWcg3wXZS9#14945407 - HappyNomad
6个回答

44
更新:我们已经在Visual Studio 2013中解决了这个问题。可移植库针对商店(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 Store 应用程序项目中引用该项目。

顺便说一下,我在 PclContrib 网站上提交了一个工作项,以添加 Timer 支持:http://pclcontrib.codeplex.com/workitem/12513


1
另外一条评论,我们发布了更新的Async包,为面向下级平台的可移植库添加了await/async支持:http://blogs.msdn.com/b/bclteam/archive/2012/10/22/using-async-await-without-net-framework-4-5.aspx - David Kean
1
预发布的异步包看起来不错,但是针对4.0版本时似乎会丢失ObservableCollection,所以我们又回到了PCL跳跃... - Henry C
1
看起来这个问题仍未解决。我们有修复的预计时间吗? - Jason Steele
1
@Rui,我们只支持在目标为“.NET 4.5.1、Windows 8.1和Windows Phone 8.1”时使用计时器。 - David Kean
1
@DavidKean 我之前经常使用 System.Threading.Timer 的 Change(int dueTime, int period) 功能,但在使用 Profile 78 的 Xamarin 上,无法使用 System.Threading.Timer,因此我现在使用你的 Timer 实现,非常感谢!你知道如何在你的 Timer 中实现 Change(int dueTime, int period) 功能吗? - dynamokaj
显示剩余9条评论

7

根据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));

    }

1
很好的想法来弥合这个差距。然而,我注意到您在构造函数中忽略了dueTime和period。 - eli
我已经实现了这个解决方案,它运行良好。但是现在我收到了警告:Xamarin.Android.Common.targets(3,3): Warning MSB3247: Found conflicts between different versions of the same dependent assembly. (MSB3247) (Prototype.Droid) 如何消除它? - Aleksei Petrenko
更改的实现在哪里? - sensei

6

实现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的答案,增加了一个新参数,如果周期小于回调运行时间,则将调用排队到您的回调中。并且用一个action替换了旧的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);
    }
}

这很有帮助,但需要做一些工作来处理毫秒周期为负数的情况,即只想运行一次的情况。 - tofutim
另外,如果 Task.Run(() => tuple.Item1(tuple.Item2)); 失败了,你将如何捕获错误? - tofutim
在你的回调函数中加入 try / catch 语句。计时器不负责捕获错误。对于一次性运行,我让你添加代码,这太容易了。 - Softlion

2

您可以使用一个PCL库创建一个计时器界面,然后使用W8S计时器在第二个W8S库中创建该接口的实现。

然后,您可以使用依赖注入将W8S库注入到PCL类中。


1
我使用了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包中。


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