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