Aleksei Petrenko
Aleksei Petrenko

Reputation: 7188

Problems with System.Threading.Timer in PCL Profile 78 - Warnings

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?

Upvotes: 3

Views: 1505

Answers (3)

Daniel Henry
Daniel Henry

Reputation: 856

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

Upvotes: 4

Ben Gladman
Ben Gladman

Reputation: 71

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.

Upvotes: 1

valdetero
valdetero

Reputation: 4652

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>

Upvotes: 3

Related Questions