33

I can't find a timer in portable library / Windows Store. (Targeting .net 4.5 and Windows Store aka Metro)

Does any have an idea on how to created some kind of timing event?

I need somekind of a stopwatch, so this should refreshn once a second or so

Boas Enkler
  • 12,264
  • 16
  • 69
  • 143
  • 2
    Hmmm - I've created an explicit Windows Store library and indeed System.Threading.Timer wasn't there. That's odd, because when I made a portable class library that also targeted Windows Store, it worked. Sounds like an error in the "what is supported" metadata for PCL. – Marc Gravell Sep 23 '12 at 18:54
  • 1
    I think the problem is that the timer in Metro isn't similar to the one in RT. Because in RT I can only find a Dispatcher timer. so this can't be mapped trhough PCL :-( – Boas Enkler Sep 23 '12 at 18:55
  • 2
    This might help you: http://stackoverflow.com/a/14945407/122781 – HappyNomad Mar 10 '13 at 01:28

6 Answers6

44

Update: We have fixed this in Visual Studio 2013. Portable libraries targeting Store (Windows 8.1) and .NET Framework 4.5.1 projects can now reference Timer.

This is unfortunate case of where our implementation details are leaking to the user. When you target just .NET 4.5 and Windows Store apps, we actually cause you to build against something different then when you target a down-level platform (.NET 4, SL 4/5, Phone 7.x). We try treat these two as the same, but limited changes underneath start to leak (such as Timer, and Reflection). We cover some of this here: http://channel9.msdn.com/Shows/Going+Deep/NET-45-David-Kean-and-Marcea-Trofin-Portable-Libraries.

We'll look at fixing this in a future version. Until then, you have a couple of workarounds:

1) Implement your own version of Timer using Task.Delay, here's a quick copy that we're using internally:

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) Downgrade your project to .NET 4.0 and Windows Store apps, which will give you access to Timer.

3) Create a new project targeting .NET 4.0 and Windows Store apps, and put the code that requires timer in that. Then reference that from the .NET 4.5 and Windows Store apps project.

As a side note, I've filed a work item for myself over on PclContrib site to add Timer support: http://pclcontrib.codeplex.com/workitem/12513.

David Kean
  • 5,722
  • 26
  • 26
  • Note the unfortunate sideeffect of going down to 4.0 means you lose async/await, so if you're targeting WP8 + Win 8 option 2 is probably not that great – Henry C Nov 18 '12 at 15:43
  • option 1) doesnt seem to call the Callback more than once - perhaps i read the code wrong, or am not using it correctly? I'm calling myTimer = new Timer(TimeEvent, null, 1000, -1); – Henry C Nov 18 '12 at 16:44
  • You're not reading the code incorrectly, that's what the first Assert is about. That code is equivalent to passing Infinite for as "period". I'll leave it up to the exercise of the reader to implement "period". :) – David Kean Nov 19 '12 at 17:37
  • 1
    Another comment, we released an updated Async pack that adds await/async support to portable libraries targeting downlevel platforms: http://blogs.msdn.com/b/bclteam/archive/2012/10/22/using-async-await-without-net-framework-4-5.aspx – David Kean Nov 19 '12 at 17:42
  • 1
    the pre-release async pack looks good, except that targeting 4.0 then seems to lose ObservableCollection so we're back to PCL hopping... – Henry C Nov 21 '12 at 16:39
  • 1
    It appears that this is still outstanding. Do we have an ETA for a fix? – Jason Steele Sep 12 '13 at 15:26
  • Jason: I'm not sure I understand. Are you saying that you can't consume Timer when targeting 4.5.1 & Windows Store 8.1? – David Kean Sep 12 '13 at 16:11
  • Can someone please show me how to modify the above code so that Period is used? I need to use this as a Timer replacement in a PCL to have a callback every 1 second. I'm not sure how to do this... :( – Maximus Nov 06 '13 at 18:41
  • I would not mark this as fixed since the resulting PCL profile (profile44) is not supported on Xamarin platforms or WP8, so I doubt that many people have a use for this profile. – tempy Jan 28 '14 at 23:54
  • Why use a tuple rather than `callback(s)`? The callback is already in scope. – N_A Apr 02 '14 at 21:14
  • Hi @DavidKean. As of today, you still can't use timer (it appears that System.Timers does not even exist) in PCL. I'm using VS2013 and targeting ".Net 4.5 and higher" + Windows Store apps (Windows 8) and higher, and Windows Phone 8. – Rui Apr 03 '14 at 20:55
  • 1
    @Rui, we only support Timer when targeting ".NET 4.5.1, Windows 8.1 & Windows Phone 8.1" – David Kean Apr 09 '14 at 17:40
  • 1
    @DavidKean I were using the System.Threading.Timer's Change(int dueTime, int period) alot, but on Xamarin using Profile 78, I am not able to use the System.Threading.Timer, hence I now use your implementation of the Timer, thanks! Due you know how the Change(int dueTime, int period) could be implemented in your Timer? – dynamokaj Feb 16 '15 at 10:42
  • dynamokaj, did you find a solution for implementing Change? – tofutim Mar 06 '15 at 19:54
7

Following suggestion #3 from David Kean, here's my hacky Timer adapter - put this in a PCL library that targets .net 4.0, and reference it from 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);
        }
    }

And then to use it, you can do this from your 4.5 PCL library:

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

    }
Henry C
  • 4,781
  • 4
  • 43
  • 83
  • 1
    Great idea to bridge this gap. However, I notice that you ignore dueTime and period in your constructor. – eli Sep 12 '13 at 10:35
  • I implemented this solution and it works fine. But now I have warning: Xamarin.Android.Common.targets(3,3): Warning MSB3247: Found conflicts between different versions of the same dependent assembly. (MSB3247) (Prototype.Droid) How to get rid of it? – Aleksei Petrenko Mar 23 '14 at 11:05
  • where is implementation of change? – sensei Aug 21 '16 at 20:16
6

Implementation of suggestion #1 from David Kean with period:

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(); }
}
Ivan Leonenko
  • 2,363
  • 2
  • 27
  • 37
4

I improved Ivan Leonenko answer by including a new parameter, which queue calls to you callback if the period is less than the callback run time. And replaced the legacy TimerCallback with an action. And finally, use our cancel token in the last delay, and used ConfigureAwait to increase concurrency, as the callback can be executed on any thread.

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);
    }
}
Softlion
  • 12,281
  • 11
  • 58
  • 88
  • This is helpful, though some work needs to be done to handle the case when millisecondsPeriod is negative, i.e., one time running is desired. – tofutim Mar 06 '15 at 20:09
  • also, how will you catch the error if Task.Run(() => tuple.Item1(tuple.Item2)); fails? – tofutim Mar 06 '15 at 21:16
  • Add a try / catch in your callback. It is not the responsibility of the timer to catch your errors. For one time runs i let you add the code, it is too easy. – Softlion Mar 20 '15 at 11:06
2

You could create a timer interface using a PCL library and then create an implementation of that interface in a second W8S library using a W8S timer.

Then you could use dependency injection to inject the W8S library into the PCL class.

Paul
  • 1,590
  • 5
  • 20
  • 41
1

I ended up with Observable.Timer from Reactive Extensions (Rx). Rx was already included in the project, so additional reference was not an issue.

Here is a timer that triggers every second:

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 class is in PCL friendly Rx-Linq NuGet package.

altso
  • 2,311
  • 4
  • 26
  • 40