Elektroda.pl
Elektroda.pl
X
Proszę, dodaj wyjątek www.elektroda.pl do Adblock.
Dzięki temu, że oglądasz reklamy, wspierasz portal i użytkowników.

c# i dokładne odliczenie okresu czasu

viper_lasser 07 Wrz 2011 21:33 4560 15
  • #1 07 Wrz 2011 21:33
    viper_lasser
    Poziom 17  

    Witam
    W jaki sposób dokładnie określić cykliczne wywołanie się danej procedury z dokładnością co do ms.

    Potrzebuje dokładnie co 1 sekundę wysłać zapytanie do urządzenia po ethernecie i odebrać odpowiedź.
    Na razie korzystam z kontrolki timer'a. Problem jest taki że jak ustawię opcję "interval" na 1000 (ms) to nie jest dokładnie odliczane 1000ms tylko ciut więcej

    09-07-2011 09:26:38.911
    09-07-2011 09:26:39.925
    09-07-2011 09:26:40.939
    09-07-2011 09:26:41.953
    09-07-2011 09:26:42.967
    09-07-2011 09:26:43.981
    09-07-2011 09:26:44.996
    09-07-2011 09:26:46.010
    09-07-2011 09:26:47.024
    09-07-2011 09:26:48.038
    09-07-2011 09:26:49.051


    Przy próbach wyszło mi tak że jak ustawię interval na 999 to dalej czas jest minimalnie zawsze zawyżany a jak ustawię na 998 ms to jest ciut za mało - zawsze odrobiona z 1s jest ucinana.

    0 15
  • #2 07 Wrz 2011 21:58
    marcinj12
    Poziom 40  

    To dlatego, że korzystając z kontrolki timer korzystasz z najmniej dokładnego timera: z przestrzeni System.Windows.Forms, uruchamianego na wątku GUI aplikacji ze wszystkimi tego konsekwencjami.
    Sprawdź któryś z przestrzeni System.Timers lub System.Threading, zdaje się ten ostatni, mimo że najbardziej "skomplikowany", jest i najdokładniejszy.

    Porównanie wraz z przykładami znajdziesz np. tutaj

    0
  • #3 07 Wrz 2011 22:38
    viper_lasser
    Poziom 17  

    @ Marcinj12
    Dzięki za sugestię

    Użyłem tego przykładu z System.Timers ale np po 10 sek odliczanych co 1s widać że minimalnie się mi czas rozbiega

    http://stackoverflow.com/questions/169332/is-...in-c-that-isnt-in-the-windows-forms-namespace

    A potrzebuję odczytywać jakieś dane z podwójnego bufora co 1 sekundę.
    Nie będzie problemu jak np co jakiś czas cykl mi się wywoła np 20ms później a na chwilkę o 20ms wcześniej. Najważniejsze żeby np po 2 minutach timer w c# był zgodny z timerem ogólno czasowym.

    0
  • #4 08 Wrz 2011 01:01
    analfabet
    Poziom 12  

    Zapytanie musi być wysyłane dokładnie co 1s, czy zadowolisz się tym, że po np. 10s nie będzie zbyt dużego rozrzutu?

    0
  • #5 08 Wrz 2011 07:01
    viper_lasser
    Poziom 17  

    Zapytanie musi być wysłane dokładnie co 1s. Chodzi mi o to że raz może być opóżnienie 1ms ale żeby później w następnej sekwencji o tą 1 ms szybciej nastąpił odczyt bo jak co każde zapytanie będę miał przyrost 1-10ms do tych ustalonych 1000ms to po jakimś czasie będę miał rozsynchronizowany timer

    0
  • #6 08 Wrz 2011 07:22
    Miglans_87
    Poziom 13  

    Może rób korektę co 10s. Pamiętaj, że sama komunikacja trochę trwa. Więc może uruchom dodatkowy TIMER, lub coś dokładniejszego i co 10-tą próbkę przyrównaj czasy.

    0
  • #7 08 Wrz 2011 16:29
    analfabet
    Poziom 12  

    viper_lasser
    Nie wiem, czy dobrze się rozumiemy. Chodziło mi o to co napisał Miglans_87- może wystarczy Ci raz na 10s porównać wskazanie timera z zegarem systemowym. W takim wypadku przecież czas nie będzie się rozjeżdżał (pytanie, czy maksymalny błąd przy 10tej sekundzie jest dla Ciebie akceptowalny?).

    Jeżeli nie takie rozwiązanie to może użycie Stopwatch? Albo coś z cyklami zegara na sekundę?

    0
  • #8 08 Wrz 2011 21:36
    Miglans_87
    Poziom 13  

    viper_lasser
    Przypadkiem nie próbujesz zsynchronizować dwa urządzenia?
    Myślałem że timer jest wystarczający. Może zrób to w wątku ?

    Robisz pętle sprawdzającą czy mineła sekunda, porównując czas z zegara systemowego (masz możliwość czytywania zegara z dokładnością do us)...

    While(true)
    {
    while(t<1s) t=pobierz_czas();

    wyślij(); odbierz();
    }

    0
  • #9 09 Wrz 2011 00:03
    marcinj12
    Poziom 40  

    Windows nie jest systemem czasu rzeczywistego, a już na pewno nie jest nią platforma .NET :) Jest raczej nastawiony na obsługę zdarzeń. Sam pomiar czasu czy porównanie dwóch czasów zajmuje ileś-tam mikrosekund, więc raczej nie zrobisz super-dokładnego zegara, który się nie będzie rozjeżdżał z czasem. W mikrokontrolerach można sobie wyliczyć ilość cykli zegara na każdą instrukcję i w miarę dokładnie coś takiego zrobić, tutaj - nie bardzo.

    Najdokładniejszy znany mi sposób to z wykorzystaniem PerformanceCounter. Na moim komputerze "późni się" ok. 30-35µs na minutę, przy wyzwalaniu co 1000ms.
    Poniżej masz kompletny przykład, jak to może wyglądać na konsoli:

    Kod: csharp
    Zaloguj się, aby zobaczyć kod


    Przykład niżej, z wykorzystaniem w miarę dokładnej klasy StopWatch, "późni się" ok. 370ms / minutę:
    Kod: csharp
    Zaloguj się, aby zobaczyć kod


    Możesz też popróbować z timerem (z System.Timers lub Theading) dla różnych ustawień interwału - może jak ustawisz np. 10x po 100ms albo 20x 50ms zamiast 1x 1000ms, to możesz uzyskać inne, dokładniejsze wyniki. Zdaje się że poniżej 10-20ms interwału nie da się zejść z timerem.

    1
  • #10 09 Wrz 2011 08:30
    viper_lasser
    Poziom 17  

    W jaki sposób mogę z innego wątku zaktualizować jakiś wykres i inne dane na windows.form

    Pojedyńczą kontrolkę to umiem zmienić w ten sposób

    Kod: csharp
    Zaloguj się, aby zobaczyć kod

    0
  • #11 10 Wrz 2011 09:32
    Miglans_87
    Poziom 13  

    Prawdopodobnie wywala Ci błąd. Z samego wątku ciężko jest ustawić coś z poza wątku.
    Robi się to przy pomocy Delegate();
    przykład:

    private delegate void Delegacja(); // tworzysz delegacje

    label.Invoke((Delegacja)delegate { label.Text = " przyklad dla viper_lasser" });

    0
  • #12 10 Wrz 2011 09:43
    viper_lasser
    Poziom 17  

    Poradziłem już sobie z problemem dokładnego odmierzania czasu korzystając z kontrolki BackgroundWorker

    Code:

    private MethodInvoker updateDelegate = null;

    ...

    updateDelegate = new MethodInvoker(GetData);




    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
            {
                while (true)
                {

                    var czas = DateTime.Now;
                    var dana = czas.ToString("MM/dd/yyyy hh:mm:ss.fff tt");
                    System.Console.WriteLine(czas.ToString(dana));

                    if (MeasureStarted)
                    {
                        Invoke(updateDelegate);
                    }
                    Thread.Sleep(1000); // Very time-consuming.

                }
            }


    gdzie metoda GetData to zwykła metoda wyświetlająca jakieś dane na wykresie
    I to działa niezawodnie.
    Gdzieś w internecie doczytałem się że windows.form.timer ma dokładność do ok 33ms.

    0
  • #13 10 Wrz 2011 14:10
    marcinj12
    Poziom 40  

    viper_lasser napisał:
    Poradziłem już sobie z problemem dokładnego odmierzania czasu

    Jesteś pewien?? :)

    Tzn. jeżeli efekt, który uzyskałeś, Ci odpowiada to OK, natomiast nie jest to rozwiązanie problemu w formie, w której to przedstawiłeś w pierwszym poście:
    viper_lasser napisał:
    Potrzebuje dokładnie co 1 sekundę wysłać zapytanie...


    W formie w jakiej masz to teraz, nie wysyłasz zapytania dokładnie co jedną sekundę, a robisz 1s przerwy po każdej "akcji" (tym wypadku: wywołaniu metody GetData delegatem updateDelegate).
    Dodatkowo, robisz to synchroniczną metodą Invoke, co w praktyce oznacza odpalanie metody na tym samym wątku, czyli "czekanie" na zakończenie metody.
    Jeżeli metoda GetData() zajmie Ci 150ms, to do facto pomiędzy kolejnymi wywołaniami upłynie nieco więcej niż 1150ms.

    Jeżeli już chcesz korzystać z metody Invoke(), lepiej użyj jej asynchronicznej wersji: BeginInvoke ("uruchom i zapomnij"). Ona uruchamia metodę i nie czeka na jej zakończenie.
    Poniżej przykład wypisujący datę do listBox'a:
    Code:
    using System;
    
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    using System.Threading;

    namespace WindowsFormsApplication1
    {
        public partial class Form1 : Form
        {
            MethodInvoker updateDelegate;
            bool dzialaj;

            public Form1()
            {
                InitializeComponent();
                updateDelegate = new MethodInvoker(GetData);
            }

            private void button1_Click(object sender, EventArgs e)
            {
                if (!dzialaj)
                {
                    dzialaj = true;
                    backgroundWorker1.RunWorkerAsync();
                }
                else
                    dzialaj = false;
            }

            private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
            {
                while (dzialaj)
                {
                    updateDelegate.BeginInvoke(null, null);
                    Thread.Sleep(1000);
                }
            }

            private void GetData()
            {
             if (this.InvokeRequired)
             {
                this.BeginInvoke(new MethodInvoker(GetData));
             }
             else
             {
                listBox1.Items.Add(DateTime.Now.ToString("MM/dd/yyyy hh:mm:ss.fff tt"));
                Thread.Sleep(150);
             }
            }

        }
    }


    To też nie jest doskonałe, bo wywołanie metody też zajmuje trochę czasu, jak widać na załączonym obrazku, różnica wystąpi (u mnie: 5ms/minutę):
    c# i dokładne odliczenie okresu czasu

    0
  • #14 10 Wrz 2011 20:17
    viper_lasser
    Poziom 17  

    @marcinj12

    Z tą rozbieżnością rzędu 5ms/min to masz rację. Wobec tego co będzie lepsze BeginInvoke czy jeszcze jakaś inna technika ?

    0
  • #15 10 Wrz 2011 21:11
    marcinj12
    Poziom 40  

    Przykład z BeginInvoke będzie "dokładniejszy" niż z samym Invoke.
    Najdokładniejszy przykład podałem Ci w poście #9, z QueryPerformanceCounter.

    Ale tak naprawdę, jeżeli potrzebujesz odczytywać dane co 1s, dlaczego one muszą być zsynchronizowane z zegarem systemowym? Przykład z postu #13 czy z timerem też będzie "działał": odczytujesz dane z tego swojego bufora, aktualizujesz wykres bieżącą datą systemową. Jeśli raz na jakiś czas wypadnie Ci jedna próbka, to użytkownik nawet tego nie zauważy. :)

    Z drugiej strony, jeżeli odczytujesz te dane z bufora np. w mikrokontrolerze przez RS'a, to wtedy niech on wysyła przez RS dane po aktualizacji bufora, a w aplikacji podepniesz się pod zdarzeniu OnDataReceived w SerialPort. Raczej w tym kierunku bym szedł: obsługi zdarzenia od urządzenia aktualizującego dane.

    Innych metod nie znam.

    0
  • #16 10 Wrz 2011 21:23
    viper_lasser
    Poziom 17  

    W mojej aplikacji odczytuję dane po modbusie tcp co 1 sekundę. Jest to tablica z 50 elementami. Dane te są buforowane w sterowniku. Raz odczytuję rejestry Holding Registers z pod adresu 40001 a drugi raz z 40101 i tak w kółko. Poprostu nie mogę co jakiś czas zgubić 1 cyklu lub go rozsynchornizować bo dane nie będą się czytały z tego buforu co trzeba i z danych pomiarowych na wykresie wyjdą schody - zamiast z jednego bufora dane odczytają się z drugiego jeszcze nie w pełni wypełnionego i bedzie wtopa.

    Sposób z Invoke chodzi w miarę ok - sprawdzałem dla paru minut ale pewnie jak oszacowałeś 5ms rozbieżności/ przyrostu na 1 min da mi ok 300ms na godzinę.

    Przy założeniach że ktoś będzie robił pomiar do 1 godziny będzie ok bo nie będzie rozsynchronizowania.

    0