
- Processes, standards and quality
- Technologies
- Others
Bawiłem się ostatnio aplikacją (WPF, .NET 4.5, C# 5 async await), która wyświetla na żywo obraz z kamerki i skanuje go w poszukiwaniu pewnych informacji. Chcąc maksymalnie zwiększyć wydajność, doszedłem do implementacji Timera, który sam dostosowuje swój interwał, aby maksymalnie zrównoleglić periodyczne operacje i wykorzystać dostępne procesory.
Może nic skomplikowanego, ale jakoś nie potrafiłem sprawnie zgooglować dokładnie za tym, czego potrzebowałem (choć pewnie jest tego już pełno ;-)).
Wymagania
Moim podstawowym wymaganiem było, aby Timer obsługiwał operacje asynchroniczne tworzone za pomocą słów kluczowych async / await i aby sam dobierał swój interwał tak, by jak najwięcej operacji działo się równolegle bez wpływu na wątek UI – chciałem, żeby obraz z kamery wyświetlał się płynnie.
Implementacja
public class AutoLoadDispatcherTimer
{Klasa jest wrapperem wokół standardowego DispatcherTimera:
private readonly DispatcherTimer _internalTimer;
W konstruktorze, zamiast delegata z zewnątrz, podłączamy własny handler:
public AutoLoadDispatcherTimer()
{
_internalTimer = new DispatcherTimer();
_internalTimer.Tick += AutoLoadCallback;
}W handlerze dzieje się wszystko, co istotne. Przede wszystkim jest oznaczony słowem „async”:
private async void AutoLoadCallback(object sender, EventArgs eventArgs)
{Za pomocą Stopwatcha mierzymy czas operacji. Operacje oczywiście wykonujemy asynchronicznie za pomocą słowa „await”:
_intervalStopwatch.Restart();
await OnTick();
_intervalStopwatch.Stop();Na podstawie czasu i ilości dostępnych procesorów wyliczamy nowy interwał. Wyliczenie jest proste – jeśli mamy np. 3 dostępne rdzenie i zadanie zajmujące 1 sekundę, to jeśli będziemy uruchamiać kolejne iteracje zadania z opóźnieniem 333 ms, powinniśmy mieć zawsze jeden wolny rdzeń, gdy któreś zadanie się skończy.
// calculate the interval, so the scanning will use all available cores
var interval = (_intervalStopwatch.ElapsedMilliseconds / _availableProcessors);
_internalTimer.Interval = TimeSpan.FromMilliseconds(interval);
}Ilość procesorów jest brana z klasy „Environment”. Domyślnie ustawienie zwraca ilość logicznych procesorów – 1, zostawiając jeden dla wątku UI.
private static int GetAvailableProcessors()
{
int availableProcessors = Environment.ProcessorCount - 1;
if (availableProcessors <= 0)
availableProcessors = 1;
return availableProcessors;
}W sumie to tyle – aplikacja, którą napisałem wyświetla na żywo obraz z WebKamerki i na moim kompie (4 logiczne procesory) potrafi osiągnąć 40 skanów na sekundę (czyli pewnie szybciej niż kamerka podaje obraz).
Poniżej załączam pełne źródła klasy:
AutoLoadDispatcherTimer.cs
