- 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