Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Timer in Portable Library

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

like image 516
Boas Enkler Avatar asked Sep 23 '12 18:09

Boas Enkler


4 Answers

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.

like image 182
David Kean Avatar answered Nov 03 '22 04:11

David Kean


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

    }
like image 39
Henry C Avatar answered Nov 03 '22 03:11

Henry C


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(); }
}
like image 6
Ivan Leonenko Avatar answered Nov 03 '22 02:11

Ivan Leonenko


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);
    }
}
like image 4
Softlion Avatar answered Nov 03 '22 03:11

Softlion