Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

WinRT Discussion :

Timer imprécis sous WinRT


Sujet :

WinRT

  1. #1
    Membre averti
    Timer imprécis sous WinRT
    Bonjour;

    Je travaille sur un jeu comprenant des pendules, mais j'ai remarqué qu'elle est imprécise sous métro alors que le même
    code sous desktop et silverlight fonctionne parfaitement (précision).
    Le timer à environ 1O% d'erreur, par exemple sur 10s il est en retard de 1.5s sur un chrono classique. C'est encore pire dans le simulateur (par
    rapport à l'ordi local) avec un retard de 3.5s sur 10s. En compilant pour le simulateur on voit même à l’œil nu que les dixièmes de secondes
    sont au ralenti.
    Voici le code (un textblock + 2 boutons en xaml):

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    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
    41
    42
    43
     
    namespace timer
    {
     
        public sealed partial class MainPage : Page
        {
            const string INITTIME = "00:00:10:0";
            const string PENDULE_FORMAT = @"hh\:mm\:ss\:f";
     
            static TimeSpan time = new TimeSpan();
            static readonly TimeSpan DIZIEME_SEC = TimeSpan.FromMilliseconds(100);
            static DispatcherTimer timer = new DispatcherTimer();
     
            public MainPage()
            {
                this.InitializeComponent();
                timer.Tick += timer_Tick;
                timer.Interval = DIZIEME_SEC;
                time = TimeSpan.FromSeconds(10);
                txtblock.Text = INITTIME;
            }
     
            void timer_Tick(object sender, object e)
            {
                time = time.Subtract(DIZIEME_SEC);
                txtblock.Text = time.ToString(PENDULE_FORMAT);
                if (time.TotalMilliseconds <= 0)
                    timer.Stop();
            }
     
            void start_Click(object sender, RoutedEventArgs e)
            {
                txtblock.Text = INITTIME;
                time = TimeSpan.FromSeconds(10);
                timer.Start();
            }
     
           void stop_Click(object sender, RoutedEventArgs e)
            {
                timer.Stop();
            }
        }
    }


    J'utilise vs2012 express pour win8. Bien sûr, je compile en release.
    Peut-être que le DispatcherTimer n'est pas adapté sous métro?
    Merci.

  2. #2
    Membre expert
    En WPF (j'imagine que c'est la même chose sous WinRT) le dispatchertimer garanti juste qu'il ne sera pas appelé avant l'intervalle voulu. Ca dépend du travail sur le dispatcher.

    Alors en Metro c'est peut-être la même chose, mais avec un dispatcher plus encombré, d'où l'imprécision.

    Je n'ai plus le code sous la main et c'était sous WP8 mais j'ai du utiliser un timer pour une app et c'était assez précis
    Microsoft MVP : Windows Platform

    MCPD - Windows Phone Developer
    MCPD - Windows Developer 4

    http://www.guruumeditation.net

    “If debugging is the process of removing bugs, then programming must be the process of putting them in.”
    (Edsger W. Dijkstra)

  3. #3
    Rédacteur/Modérateur

    Citation Envoyé par Fabiani Voir le message
    Peut-être que le DispatcherTimer n'est pas adapté sous métro?
    Si tu as besoin d'une bonne précision, il n'est pas adapté tout court (même en WPF ou Silverlight)... Ca peut marcher à peu près correctement si le thread UI n'est pas trop occupé, mais c'est sans garantie. Si tu as besoin d'un timer précis, utilise System.Threading.Timer

  4. #4
    Membre averti
    Bonjour,

    Ok, merci à vous, c'est un peu ce que je pensais mais j'avais aussi un doute sur mon code.

    Salutations.

  5. #5
    Membre averti
    Bonjour,

    Pour ceux que ça intéresse, j'ai finalement utilisé un ThreadPoolTimer, voici la partie principale :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
     
     
    threadPoolTimer = ThreadPoolTimer.CreatePeriodicTimer((source) =>
                {
                   timePool = timePool.Subtract(DIZIEME_SEC);
     
                 //pour modifs UI
                 Dispatcher.RunAsync(CoreDispatcherPriority.High, () => 
                 { 
                    txtblockpool.Text = timePool.ToString(PENDULE_FORMAT); 
                 }).AsTask().Wait();
     
                }, TimeSpan.FromMilliseconds(100));

    La précision est au rendez-vous maintenant.
    Merci.

  6. #6
    Expert confirmé
    Cette solution foirera de la même façon lorsque le CPU sera débordé, c'est ton approche générale qui est mauvaise.

    Problèmes :
    a) Il faut comprendre que toutes les valeurs communes de temps sous Windows n'ont qu'une précision de 15ms par défaut (Thread.Sleep, DateTime.Now, ThreadPoolTimer, etc) C'est d'ailleurs aussi le cas sous les autres OS : sur Linux la précision par défaut n'est que de 1ms. Si tu as besoin de quelque chose de plus précis, il faut des timers dits "multimédias" ou "haute résolution", comme celui de la classe Stopwatch. Bref, en demandant un tic toutes les 100ms, tes tics réels sont en fait espacés de 90ms parfois et de 105ms d'autres fois. 105, 210, 300, 405, etc. Tu aurais donc toujours au moins 5ms à 10ms d'erreur, même si au final elles tendent à se compenser. Note aussi que sur certains systèmes Windows cette durée peut être plus faible ou plus grande, 15 n'est que la valeur par défaut.

    b) Quand un tic se produit ton code est seulement mis en file. Mais il ne sera exécuté que lorsqu'une tranche CPU sera disponible. Et, en plus, dans ton premier code, tu réclamais spécifiquement une exécution sur le thread UI donc il fallait aussi que celui-ci soit libre. Dans tous les cas il y a une file d'attente, ton code n'est jamais exécuté immédiatement et tu peux avoir plusieurs tics d'affilée à traiter.


    Solutions:
    a) N'incrémente/décrémente pas manuellement une durée à chaque tic. Stocke l'heure de début et calcule dynamiquement à chaque tic la durée écoulée en faisant (DateTime.Now - début). De cette façon tu n'empiles pas les erreurs et tu gères correctement le cas où ton code est resté trop longtemps en file. Attention, note que tu pourrais détecter plusieurs fois la fin et que tu pourrais toujours recevoir des tics après avoir supprimé l'abonnement à ton timer.

    b) SI tu as besoin d'un temps précis pour ajuster les animations entre deux images, 15ms est trop grossier: à 60 ips DateTime.Now varierait de 0ms, 15ms ou 30ms entre deux images, selon les fois. Il te faut donc un timer haute-performances (StopWatch). Tu relèves une fois au début de chaque image la valeur ElapsedMillisecond et tu calcules toutes tes animations en fonction de ce temps. Attention, l'appel à Stopwatch.ElapsedMilliseconds est coûteux.

  7. #7
    Membre averti
    DonQuiche,

    J'ai hésité avec StopWatch, mais comme "de visu" ça parait plus précis j'ai opté pour un ThreadPoolTimer.
    Comme tu le précises la fourchette peut tomber au dessus ou en dessous donc ça compense au final c'est
    pour ça que ça "parait" correct.
    Merci pour tes précisions car en général je suis assez pointilleux mais là j'ai déjà pris du retard donc j'ai un peu
    laissé les détails, mais je vais prendre un peu de temps pour tester StopWatch.
    Merci encore.

    Salutations.

###raw>template_hook.ano_emploi###