14 maja 2016

Async command #2

Async command z opóźnieniem

Przykład zastosowania klasy DelayedSingleCommand:

  • w odtwarzaczu video ukrywamy kursor myszki 5 sekund po ostatnim poruszeniu myszką,
  • przetwarzamy ponownie dany plik, jeśli uległ modyfikacji, ale przez ostatnie 100 sekund już nie był modyfikowany ponownie.

Opis działania klasy:

  • w chwili pojawienia się wywołania rozpoczynamy odliczanie czasu Δ
  • jeśli w czasie oczekiwania Δ pojawiło się kolejne wywołanie, resetujemy licznik czasu
  • jeśli czas Δ upłynie, wywołujemy wówczas kontrolowaną funkcję
  • kontrolowana funkcja będzie wywołana w kontekście wątku tworzącego obiekt lub w wątku timera zależnie od parametru przekazanego w konstruktorze
  • ewentualny wyjątek łapiemy i przekazujemy do funkcji obsługi wyjątków

Implementacja

using System;
using System.Threading;
using Timer = System.Threading.Timer;

namespace Demo.Commands
{
    public class DelayedSingleCommand
    {
        public DelayedSingleCommand(
            Action task,
            Action<Exception> errorTask,
            int delay,
            bool useThisContex = true )
        {
            if (useThisContex)
                _contex = SynchronizationContext.Current;
            _task = task;
            _errorTask = errorTask;
            _delay = delay;
            if (_task == null) throw new ArgumentNullException();
            _timer = new Timer(TimerCb,
                               null,
                               Timeout.Infinite,
                               Timeout.Infinite);
        }

        public void Execute()
        {
            _timer.Change(_delay, Timeout.Infinite);
        }

        public void Cancel()
        {
            _timer.Change(Timeout.Infinite, Timeout.Infinite);
        }

        //=======================================

        private readonly SynchronizationContext _contex;
        private readonly Action _task;
        private readonly Action<Exception> _errorTask;
        private readonly int _delay;
        private readonly Timer _timer;

        private void InvokeTask(Action task)
        {
            if (_contex == null)
            {
                task.Invoke();
                return;
            }
            _contex.Send( b => task.Invoke(), null);
        }

        private void TimerCb(object state)
        {
            try
            {
                InvokeTask(_task);
            }
            catch(Exception e)
            {
                if (_errorTask != null)
                    InvokeTask(() => _errorTask.Invoke(e));
            }
        }
    }
}

Kilka testów

using System;
using System.Threading;
using Demo.Commands;
using NUnit.Framework;

namespace Demo.Tests
{
    [TestFixture]
    public class DelayedSingleCommandTest
    {
        [Test]
        public void Test1()
        {
            var state = 0;
            var obj = new DelayedSingleCommand(() => ++state, null, 100, true);
            obj.Execute();
            Assert.AreEqual(0, state);
            Thread.Sleep(200);
            Assert.AreEqual(1, state);
        }

        [Test]
        public void Test2()
        {
            var state = 0;
            var obj = new DelayedSingleCommand(() => ++state, null, 100, false);
            obj.Execute();
            Assert.AreEqual(0, state);
            Thread.Sleep(200);
            Assert.AreEqual(1, state);
        }

        [Test]
        public void Test3()
        {
            var state = 0;
            var obj = new DelayedSingleCommand(() => ++state, null, 100, true);
            obj.Execute();
            obj.Execute();
            obj.Execute();
            obj.Execute();
            Assert.AreEqual(0, state);
            Thread.Sleep(80);
            Assert.AreEqual(0, state);
            obj.Execute();
            Thread.Sleep(80);
            Assert.AreEqual(0, state);
            Thread.Sleep(50);
            Assert.AreEqual(1, state);
        }

        [Test]
        public void Test4()
        {
            var state = 0;
            var obj = new DelayedSingleCommand(() => ++state, null, 100, false);
            obj.Execute();
            obj.Execute();
            obj.Execute();
            obj.Execute();
            Assert.AreEqual(0, state);
            Thread.Sleep(80);
            Assert.AreEqual(0, state);
            obj.Execute();
            Thread.Sleep(80);
            Assert.AreEqual(0, state);
            Thread.Sleep(50);
            Assert.AreEqual(1, state);
        }

        [Test]
        public void Test5()
        {
            var state = 0;
            var obj = new DelayedSingleCommand(() => ++state, null, 100, true);
            obj.Execute();
            obj.Execute();
            obj.Execute();
            obj.Execute();
            Assert.AreEqual(0, state);
            Thread.Sleep(80);
            obj.Cancel();
            Assert.AreEqual(0, state);
            Thread.Sleep(80);
            Assert.AreEqual(0, state);
        }

        [Test]
        public void TestError1()
        {
            bool errorProcessed = false;
            var tesk = new Action(() => { throw new ApplicationException(); } );
            var errorTask = new Action<Exception>(e => { errorProcessed = true; ; });
            var obj = new DelayedSingleCommand(tesk, errorTask, 50, true);
            obj.Execute();
            Thread.Sleep(100);
            Assert.AreEqual(errorProcessed, true);
        }

        [Test]
        public void TestError2()
        {
            bool errorProcessed = false;
            var tesk = new Action(() => { throw new ApplicationException(); });
            var errorTask = new Action<Exception>(e => { errorProcessed = true; });
            var obj = new DelayedSingleCommand(tesk, errorTask, 50, false);
            obj.Execute();
            Thread.Sleep(100);
            Assert.AreEqual(errorProcessed, true);
        }

    }
}

Odnośniki