3

I was surprised that convenient System.Threading.Timer class does not exist in Profile 78 libraries. To use this class I created another PCL which targets 4.0 framework and wrote a simple wrapper around (as it was suggested in one blog post):

public class PCLTimer
{
    private Timer timer;
    private Action<object> action;

    public PCLTimer (Action<object> action, object state, int dueTimeMilliseconds, int periodMilliseconds)
    {
        this.action = action;
        timer = new Timer (PCLTimerCallback, state, dueTimeMilliseconds, periodMilliseconds);
    }

    private void PCLTimerCallback (object state)
    {
        action.Invoke (state);
    }

    public bool Change (int dueTimeMilliseconds, int periodMilliseconds)
    {
        return timer.Change (dueTimeMilliseconds, periodMilliseconds);
    }
}

Now I can reference this 4.0 library and use PCLTimer in main PCL library. But when I try to build my main Android project I get following warnings:

Warning CS1684: Reference to type 'System.Threading.Timer' claims it is defined in 'c:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETPortable\v4.5\Profile\Profile78\mscorlib.dll', but it could not be found (CS1684) (Prototype.Core)

Warning MSB3247: Found conflicts between different versions of the same dependent assembly. (MSB3247) (Prototype.Droid)

How to get rid of these warnings properly?

Aleksei Petrenko
  • 6,698
  • 10
  • 53
  • 87
  • Please see http://stackoverflow.com/questions/12555049/timer-in-portable-library - including the update from the PCL team in ms – Stuart Mar 24 '14 at 20:58
  • Yes I saw that question and I implemented something like 3rd solution from the accepted answer. Now I'm asking about these warnings. What about fix for 4.5.1 PCL libraries - is it not available in Xamarin studio yet or what? I mean I cannot reference Timer, it does not exist in System.Threading namespace in my PCL. – Aleksei Petrenko Mar 24 '14 at 21:06
  • 1
    I would suggest not using the answer that just plugs in a .NET 4.0 Timer and instead building your own timer class using Task.Delay such as in http://stackoverflow.com/a/21095323/957673. This avoids the problem of having a reference to System.Threading.Timer entirely. – chkimes Mar 24 '14 at 21:37
  • I like Timer class, I used it in many projects and it tested zillion times. I do not want to implement my own class for such a simple thing. But if there is nothing left maybe I'll have to) – Aleksei Petrenko Mar 24 '14 at 22:50

3 Answers3

4

Here is a complete re-implementation of the Timer class that temporarily disappeared in Profile 78, using asynchronous tasks:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace Quantum
{
    public delegate void TimerCallback(object state);

    public sealed class Timer : IDisposable
    {
        private static Task CompletedTask = Task.FromResult(false);

        private TimerCallback Callback;
        private Task Delay;
        private bool Disposed;
        private int Period;
        private object State;
        private CancellationTokenSource TokenSource;

        public Timer(TimerCallback callback, object state, int dueTime, int period)
        {
            Callback = callback;
            State = state;
            Period = period;
            Reset(dueTime);
        }

        public Timer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period)
            : this(callback, state, (int)dueTime.TotalMilliseconds, (int)period.TotalMilliseconds)
        {
        }

        ~Timer()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool cleanUpManagedObjects)
        {
            if (cleanUpManagedObjects)
                Cancel();
            Disposed = true;
        }

        public void Change(int dueTime, int period)
        {
            Period = period;
            Reset(dueTime);
        }

        public void Change(TimeSpan dueTime, TimeSpan period)
        {
            Change((int)dueTime.TotalMilliseconds, (int)period.TotalMilliseconds);
        }

        private void Reset(int due)
        {
            Cancel();
            if (due >= 0)
            {
                TokenSource = new CancellationTokenSource();
                Action tick = null;
                tick = () =>
                {
                    Task.Run(() => Callback(State));
                    if (!Disposed && Period >= 0)
                    {
                        if (Period > 0)
                            Delay = Task.Delay(Period, TokenSource.Token);
                        else
                            Delay = CompletedTask;
                        Delay.ContinueWith(t => tick(), TokenSource.Token);
                    }
                };
                if (due > 0)
                    Delay = Task.Delay(due, TokenSource.Token);
                else
                    Delay = CompletedTask;
                Delay.ContinueWith(t => tick(), TokenSource.Token);
            }
        }

        private void Cancel()
        {
            if (TokenSource != null)
            {
                TokenSource.Cancel();
                TokenSource.Dispose();
                TokenSource = null;
            }
        }
    }
}
Daniel Henry
  • 856
  • 7
  • 18
3

Do you need to add a binding to the app.config? I had to do something similar for HttpClient when I added an WP8 project.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="System.Net.Http" 
                          publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="2.0.5.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>
valdetero
  • 4,624
  • 1
  • 31
  • 46
  • I tried to do something like that, but it did not help. Obviously I have some problems with mscorlib.dll. There are two of them - one referenced by 4.5 PCL and one from 4.0 PCL. I need to figure out how to eliminate the ambiguity. – Aleksei Petrenko Mar 24 '14 at 20:51
1

Instead of putting the Timer wrapper implementation into a separate .net 4.0 project, I have solved this issue a different way:

I created an ITimerWrapper interface in the core project, and put sepearate implementations in the Droid and WinPhone projects. Then I use IoC to set up the required implementation at app start.

There is no conflict between the different PCL versions of Timer using this method.

Another advantage is I can now use DispatcherTimers in the WinPhone project - I could only use System.Threading.Timers when I was sharing the code between Android and WinPhone.