Interpolierter, flexibler Vorgang mit fester Dauer bei Verwendung von Sleep in einer Schleife

Interpolierter, flexibler Vorgang mit fester Dauer bei Verwendung von Sleep in einer Schleife

Ein besserer Titel ist mir leider nicht eingefallen.

Prinzipiell geht es um eine normale Schleife, bei der man einen Vorgang über einen bestimmten Zeitraum interpoliert durchführen möchte.

Normalerweise hat man die gewünschte Gesamtdauer, sowie den gesamten auszuführenden Vorgang, teilt dann die Gesamtdauer in bestimmte kleine Schritte ein, berechnet dementsprechend passende Schritte für den auszuführenden Vorgang, und erstellt dann eine for-Schleife:

1
2
3
4
5
for (int i = 0; i < steps; ++i)
{
    Fortschritt += step;
    Sleep(dauer);
}

So eine Schleife hat ein großes Problem: System.Threading.Thread.Sleep ist extrem ungenau. Typischerweise hat es eine Auflösung von nicht weniger als 15ms. Hat man also seine Schritte z. B. für eine Auflösung von 5ms berechnet, wird die Ausführung der Schleife 3x so lange dauern wie erwartet.

Auch läßt sich bei einem Sleep schlecht vorhersagen, auch innerhalb der 15ms (Un-)Genauigkeit, wie lange es dauern wird, da diese Zeit sehr abhängig von der aktuellen Systemauslastung ist.

Ich habe also eine flexiblere Schleife entworfen, die als Eingabeparameter die Gesamtzeit nimmt. Daraus wird dann eine Schleife mit einem kleinen Sleep berechnet. Statt die dem Sleep übergebene Zeit als tatsächlich verstrichene Zeit anzusehen wird hier eine Stopwatch bemüht, so erhalten wir stets genaue Messungen der vergangenen Zeit. Ferner gibt es noch die Möglichkeit, eine Maximalgröße für die Zeit zwischen 2 Schritten anzugeben, wenn eine weiche Interpolation wichtiger ist als eine ungefähr passende Ausführungsdauer.

Am Schluß wird noch einmal sichergestellt, daß man genau beim erwarteten Endpunkt der Interpolation landet.

Um das Ganze schön flexibel zu halten, wird ferner eine Action action als Parameter übergeben, die wiederum als Parameter einen Fortschritt nimmt, zwischen 0 und 1, und sich so selber um die Interpolation (die ja von Fall zu Fall unterschiedlich sein kann) kümmertm, also z. B. um einen Weg zwischen zwei Bildschirmkoordinaten, eine schrittweise Veränderung der Transparenz, usw.

Das sieht dann so aus:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
[DllImport("winmm.dll")]
internal static extern uint timeBeginPeriod(uint period);
 
[DllImport("winmm.dll")]
internal static extern uint timeEndPeriod(uint period);
 
/// <summary>
/// perform an action for a set duration several times with a flexible loop
/// </summary>
/// <param name="action">action to perform, taking an integer parameter which is the elapsed time in ms</param>
/// <param name="ms">total ms for loop to take (roughly)</param>
/// <param name="maxstepms">if more than this time was passed between 2 iterations, include extra steps, possibly enlarging running time</param>
public static void TimedLoop(Action<float> action, int ms, int maxstepms)
{
    const int sleepms = 5;
    timeBeginPeriod(sleepms);
    System.Threading.Thread thread = System.Threading.Thread.CurrentThread;
    var prio = thread.Priority;
    thread.Priority = System.Threading.ThreadPriority.Highest;
    System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
    watch.Start();
    int lastms = 0;
    while (true)
    {
        int curms = (int)watch.ElapsedMilliseconds;
        if ((curms - lastms) > maxstepms)
            curms = lastms + maxstepms;
        if (curms >= ms)
            break;
        float percentage = (float)curms / ms;
        action(percentage);
        System.Threading.Thread.Sleep(sleepms);
        lastms = curms;
    }
    timeEndPeriod(sleepms);
    thread.Priority = prio;
    // set to final state
    action(1f);
    System.Threading.Thread.Sleep(sleepms);
}

Jede Iteration wird ca. 5ms dauern. In der Tat läßt sich an einigen System über die Benutzung von timeBeginPeriod/timeEndPeriod die Auflösung des Schedulers so ändern, daß auch Sleep eine höhere Genauigkeit aufweist. Dazu kommt natürlich noch die Dauer der Ausführung der Aktion. Diese wird jedoch über die Verwendung der Stopwatch automatisch mit einbezogen.

Durch die erhöhte Ausführungspriorität sollten wir weiter sicherstellen, daß wir am Ende eines Sleeps direkt drankommen, ohne das System übermäßig zu überlasten (wir verändern ja nur die Thread-Priorität, nicht die Prozess-Priorität, die hier einen größeren Einfluß hätte und das System deutlich negativ beeinflussen könnte – bei entsprechender Last, die unsere Action produziert).

Diese Schleife sollte sich für eine Reihe von Anwendungen benutzen lassen.

One thought on “Interpolierter, flexibler Vorgang mit fester Dauer bei Verwendung von Sleep in einer Schleife

  1. Hallo,

    ich habe mich jetzt bis Seite 5 durchgelesen und ich muss sagen:
    Die Idee mit den grünen Infoboxen gefällt mir sehr.
    Auch, dass du mit Kommentaren deine eigenen Artikel auch später noch aktualisierst ist fein.

    Insgesamt hast du nun einen Leser mehr 🙂

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.