Lista działa dziwnie

0

Problem jest następujący: w jednym wątku mam pętle która symuluje wrzucanie danych z rs'a do rejestru fifo (tutaj jest to po prostu lista.Add), a w drugiem maluję wykresy jednocześnie odpowiednie wyrzucając dane z rejestru (metoda lista.ramveAt(0)). Oto kod symulujący wrzucanie do rejestru

while (true)
for (int k = 0; k < InternalBufferEKG.Count/2; k++)
{

                    for (int l = 0; l < panels_nr; l++)
                    {
                            if (RsBufferEKG1[l].Count < 200)
                                RsBufferEKG1[l].Add(InternalBufferEKG[k * 2]);
                            else
                                RsBufferEKG1[l][RsBufferEKG1[l].Count-1] = InternalBufferEKG[k * 2];

                            if (RsBufferEKG2[l].Count < 200)
                                RsBufferEKG2[l].Add(InternalBufferEKG[k * 2]);
                            else
                                RsBufferEKG2[l][RsBufferEKG2[l].Count-1] = InternalBufferEKG[k * 2];

                            if (RsBufferEKG3[l].Count < 200)
                                RsBufferEKG3[l].Add(InternalBufferEKG[k * 2]);
                            else
                                RsBufferEKG3[l][RsBufferEKG3[l].Count-1] = InternalBufferEKG[k * 2];

                            if ((RsBufferPuls[l].Count < 200) && ((k & 0x00000001) == 0))
                                RsBufferPuls[l].Add(InternalBufferPuls[k]);
                            else if ((RsBufferPuls[l].Count >= 200) && ((k & 0x00000001) == 0))
                                RsBufferPuls[l].RemoveRange(20, 179);

                            if ((RsBufferCO2[l].Count < 200) && ((k & 0x00000001) == 0))
                                RsBufferCO2[l].Add(InternalBufferCO2[k >> 2]);
                            else if ((RsBufferCO2[l].Count >= 200) && ((k & 0x00000001) == 0))
                                RsBufferCO2[l].RemoveRange(20, 179);
                       
                    }
                    System.Threading.Thread.Sleep(10);
                }

Jak widać wrzucam w różne rejestru dane. Jezeli rejestr się przepełni to zamieniam najnowszą daną na obecną.
I teraz w innym wątku maluję wykresu (odpowiednie PictureBox.invoke(...)):

while (true)
{
#region Puls&EKG
//sprawdzenie czy w buforze cos jest
if ((RsBufferEKG1[i].Count > 0)&&(RsBufferEKG2[i].Count > 0)&&(RsBufferPuls[i].Count > 0))
{

                CurrTimeEKGPuls[i] += TimeStep[i];   //takie tam dla obrazka parametry
                if (CurrTimeEKGPuls[i] > CurrPosEKGPuls[i])
                //mniej istotny fragment
                {
                    CurrPosEKGPuls[i]++;
                    if (CurrPosEKGPuls[i] > PictureLength)
                    {
                        CurrPosEKGPuls[i] = 0;
                        CurrTimeEKGPuls[i] = 0;
                    }
                    else
                    {      
                       //tutaj sa malowania                      
                        EKGandPULSpictureBOX[i].Invoke(d1, i); //rysowanie EKG1
                        if (SignalVisible[i,0] > 1)
                            EKGandPULSpictureBOX[i].Invoke(d2, i); //rysowanie EKG2
                        if (SignalVisible[i, 0] > 2)
                            EKGandPULSpictureBOX[i].Invoke(d3, i); //rysowanie EKG3
                        if (SignalVisible[i, 1] == 1)
                            EKGandPULSpictureBOX[i].Invoke(dPuls, i); //rysowanie Puls
                        if (VisibleSmallPanel[i,1])
                            PulsBox[i].Invoke(dPulsBox, i);
                        EKGandPULSpictureBOX[i].Invoke(dClear, i);
                    }
                }

                //wyrzucanie z rejestru
                RsBufferEKG1[i].RemoveAt(0);
                if (SignalVisible[i, 0] > 1) RsBufferEKG2[i].RemoveAt(0);
                if (SignalVisible[i, 0] > 2) RsBufferEKG3[i].RemoveAt(0);
                if (SignalVisible[i, 1] == 1) RsBufferPuls[i].RemoveAt(0);
            }
            else
                System.Threading.Thread.Sleep(1);
            #endregion 

}

No i to jest w inny wątku który jak widać kręci się w kółko czekając na dane. Tylko że to dziadostwo jakoś nie działa mi jak fifo. Szybko bufor się zapełnia (nawet jak dam duże czasy wrzucania to i tak sie zapełnia i przebieg jest jekby dwa razy szybszy:/). Coś jest na pewno nie tak tylko co :/

0

No i dupa. W ogóle nie zastosowałeś się do mich rad.
A taką fajną fifo ci zrobiłem :/ akurat nadającą się tu jak ulał.
http://4programmers.net/Forum/499613?h=#id499613

  1. Nie zabezpieczyłeś się lock'iem. Scenariusz może być taki:

[Wątek 1] RsBufferEKG1[l][RsBufferEKG1[l].Count-1] = InternalBufferEKG[k * 2];

  • wątek zdąży odczytać Count, później zmiana kontekstu
    [Wątek 2] RsBufferEKG1[i].RemoveAt(0);
  • drugi wątek ściąga element, odczytany przez pierwszy wątek Count jest już nieprawidłowy
    [Wątek 1] RsBufferEKG1[l][RsBufferEKG1[l].Count-1] = InternalBufferEKG[k * 2];
  • wątek pierwszy dokańcza tą instrukcję odwołując się do komórki, której już nie ma
  1. Za dużo list. Użyj tylko jednej, bo kilka list to same starty i nieudogodnienia. Widzę, że niektóre dane co 2 klatki ślesz, więc wysyłaj je dwukrotnie tą samą klatkę. Wszystko spakuj do jednej paczki, żeby między wątkami była przekazywana tylko jedne referencja.

  2. W ogóle to co napisałeś jest bez sensu. Klatki są malowane bez wytchnienia. Użycie procka skoczy do 100 %.

  3. Bez sensu jest też to, że przekazujesz dane z jednego wątku do drugiego, aby odpalić w kontekście trzeciego. Nie potrzebujesz tylu wątków. Daj sobie spokój z Invoke i zrób to jak człowiek.

  4. I bardzo dobrze, że bufor się zapełnia. O to chodzi. Po prostu malowanie nie nadąża za danymi.

  5. Jak w buforze brakuje miejsca to podmieniaj najstarszą klatkę, a nie najnowszą, aby dane były maksymalnie aktualne. Tak działa struktura ronda.

  6. 200 klatek to dużo za dużo. Wyświetlając 20 klatek/s dostaniesz 10s opóźnienia.

0

jak wywołuję nie przez Invoke tylko normalnie wkładam tam kod to mam ten problem że jak stworze już Graphics g = PictureBox[i].CreateGraphics() i potem używam g.DrawLine(..) to mi wywala błąd, że obiekt jest używany gdzie indziej, mimo że ten PictureBox[i] został utworzony w tym samym wątku. Jak dam Invoke i tam umieszczę ten kod to wtedy dopiero działa. (Nawet jak mam ten fragment zabezpieczony lock(PictureBox[i]) { ... } to też nie działa bezpośrednio w kodzie wątku .. tylko w Invoke daje rade) W sumie wygląda to tak że jeden przebieg jedzie (na jednym pictureBox) a pozostałe wyrzucają błąd i się zatrzymują po chwili.
Zrobiłem tak że w wątku danego PictureBoxa[i] mam po stworzeniu wszystkiego co trzeba następujący kod:

while (true)
{
#region Puls&EKG
sbyte Data = RsBufferEKG1[i].Pop();
if (Data != 0)
{
CurrTimeEKGPuls[i] += TimeStep[i];
if (CurrTimeEKGPuls[i] > CurrPosEKGPuls[i])
{
CurrPosEKGPuls[i]++;
if (CurrPosEKGPuls[i] > PictureLength)
{
CurrPosEKGPuls[i] = 0;
CurrTimeEKGPuls[i] = 0;
}
else
{
Graphics g2 = EKGandPULSpictureBOX[i].CreateGraphics();
Graphics gBitmap2 = Graphics.FromImage(EKGandPULSpictureBOX[i].Image);
if (CurrPosEKGPuls[i] > 1)
{
g2.DrawLine(pEKG, CurrPosEKGPuls[i] - 1, (short)(Izolines[i, 0] - VoltMult[i] * LastEKG1[i]), CurrPosEKGPuls[i], (short)(Izolines[i, 0] - VoltMult[i] * Data));
gBitmap2.DrawLine(pEKG, CurrPosEKGPuls[i] - 1, (short)(Izolines[i, 0] - VoltMult[i] * LastEKG1[i]), CurrPosEKGPuls[i], (short)(Izolines[i, 0] - VoltMult[i] * Data));
}
LastEKG1[i] = Data;
//EKGandPULSpictureBOX[i].Invoke(dClear, (byte)i);
g2.Dispose();
gBitmap2.Dispose();

                    }
                }
            }
            else
                System.Threading.Thread.Sleep(1);

Jeżeli mam tylko jeden wątek czyli jeden PictureBox jest wyśweitlany np PictureBox[0] i on ma ten swój wątek to wtedy działa wszystko ok. Jak Stworze kilka wątków i kazdy tworzy swojego PictureBox[i] i potem ma to co powyżej to wtedy jest ten problem że na linijce g2.Drawline(..) lub gBitmap2(..) wywala że obiekt jest uzywany gdzie indziej.

0

Musisz porozdzielać zadania na 2 wątki. Wątek główny niech zajmuje się PictureBox'ami, ich tworzeniem i malowaniem po nich. Wątek roboczy niech odbiera dane z RS i je przetwarza.

Jedynym wspólnym elementem są przetworzone dane do wyświetlenia. Trzeba je jakoś przekazać z jednego wątku do drugiego. Ze względów o których pisałem w tamtym wątku pasuje tutaj struktura ronda. Tak więc synchronizacji wymaga jedynie wkładanie i wyciąganie elementów do/z ronda.

user image

Skorzystaj z tego kodu co ci napisałem
http://4programmers.net/Forum/499748#id499748
lub samemu oprogramuj jakąś kolejkę tylko mądrze, z lock'ami.

Twój wątek roboczy mógłby wyglądać tak:

while (true)
{
   for (int k = 0; k < InternalBufferEKG.Count/2; k++)
   {
      //Tworzysz nową porcję danych zawierającą po jednej wartości z każdego panelu (pojedyncza kreseczka na wykresie) 
      //Dodatkowo w danych zapisujesz numer kreski 'k' (numer kolumny)
      Dane dane = new Dane(k);
      for (int l = 0; l < panels_nr; l++)
      {
         //Tutaj wypełniasz dane         
      }
      fifo.push(dane); //dane wczytane, wysyłasz
      System.Threading.Thread.Sleep(10);
   }
}

A wątek główny (procedura malująca) tak:

Data data = fifo.Pop(); //bierzesz dane
if(data) //jeśli kolejka nie była pusta
{
   for (int l = 0; l < panels_nr; l++)
   {
      //tutaj malujesz dane
      //domalowujesz kreskę do tej kolumny, o której dane otrzymałeś
      //jeśli jakieś kolumny zostały zgubione (wątek główny nie nadąża) to kreska
      //będzie poprowadzaona przez kilka z nich, coś a'la:
      LineTo(
         dane.k, //numer kolumny
         dane[l] //wartość odczytana z rs
         );
      //do tego trzeba dorobić "zawijanie" na brzegach wykresu
   }
}

Teraz procedurę malującą wywołuj w timerze (jest w kontrolkach na zakładce Components)

0

Niestety jest problem - nie mogę dać wątku rysującego w timerze ze względu na to że musi być zachowana skala rysunku. (np 10mm/s, a dane przychodzą powiedzmy 200Hz) Z tego względu mam pewną zmienną np EKGtime którą odpowiednio powiększam w momencie sciągnięcia z bufora danej czyli EKGtime += TimeStep i wtedy w momencie przekroczenia wartości całkowitej w EKGtime można rysować. Zapewnia to zachowanie skali. Nie wiem jak można by to rozwiązać z timerem...

0

Pisałem o przekazaniu w danych numeru kolumny, której te dane się tyczą - dzięki niemu zachowasz skalę.
To znaczy wysyłasz razem z danymi czas ich nadejścia.

Nie możesz wyświetlać danych w tej samej częstotliwości co czytasz, bo komp może nie nadążyć. Część ramek może być gubiona.

0

Jak tam programik ? Do głowy wpadły mi 3 poważne pomysły.

Podczas jednego malowania ściągać z kolejki i malować wszystkie kreski. Wtedy kilka nienadążających kresek nie będzie gubiona, tylko malowana hurtem. W efekcie wyeliminowane zostaną krótkie przerwy danych.

Zamiast timera w GUI wykorzystać inny mechanizm. Taki, który wywoła funkcję malującą wtedy, kiedy będą dostępne jakieś dane oraz wątek będzie miał wolną chwilę. Można to zrealizować WaitHandle'm z zarejestrowanym callbackiem w wątku głównym (RegisterWaitForSingleObject).
Teraz krótko jak to działa. WaitHandle może być w 2 stanach które możemy zmieniać - signaled i non-signaled. Dzięki RegisterWaitForSingleObject, w momencie przejścia ze stanu non-signaled do stanu signaled wywoływany jest zarejestrowany delegat (w kontekście tego wątku, który go zarejestrował).
WaitHandle byłby sygnalizowany (stan zmieniany na signaled) gdy kolejka staje się nie-pusta, resetowany (stan zmieniany na non-signaled) kiedy kolejka staje się pusta. Coś podobnego do pipe.

Trzecią rzeczą jest rozdzielczość danych. Jeśli by oznaczyć:
X - ilość pojedynczych kresek na wykresie wciągu jednego cyklu (od lewej do prawej krawędzi)
Y - ilość pojedynczych pomiarów z RS wciągu jednego cyklu
Wtedy w wątku roboczym odbieraj po n=Y/X pomiarów i wysyłaj do wątku głównego ich średnią. Dzięki temu jedna paczka danych będzie zawierać tyle danych ile potrzeba do namalowania jednej kreski.

A więc aby kolejka sygnalizowała WaitHandle gdy staje się nie-pusta i resetowała go gdy staje się pusta można w ten sposób ją zmodyfikować:
http://pastebin.4programmers.net/156978

Reszta (wątek główny i roboczy) mógłby przyjąć taki kształt:
http://pastebin.4programmers.net/157206

Po włączeniu wątku roboczego trzeba co jakiś czas w wątku głównym (Timer) czyścić pamięć (GC.Collect())

1 użytkowników online, w tym zalogowanych: 0, gości: 1