W ktorych watkach wykonywane sa subskrybcje eventow?

0

Dzien dobry,

Mam pytanie dotyczace wywolywania subskrybcji eventow. Aby latwiej zrozumiec problem,przedstawiam ponizej przyklad aplikacji.
Jest to uproszczony przyklad (I tylko czesc aplikacji), wiec prosze sie nie czepiac skaldni, chodzi bardziej o logike zagadnenia.
Aplikacja odbiera request z OPC servera (z PLC), na podstawie tego wyszukuje danych w bazie danych (niekoniecznie na tej samej maszynie) i wysyla te dane do OPC servera (do PLC)
Mam trzy klasy classy.
OPCInterface - odbiera i wysyla dane dane z/do servera OPC
DataMgr - just for track data flow
SQLInterface - obsluga DB

public class OPCServerInterface {

 private void DataChangedHandler(object sender, DataChangeEventArgs e)
        {
            for (int i = 0; i < e.sts.Length; ++i)
            {
                if (HRESULTS.Succeeded(e.sts[i].Error))
                {
                    _ReadItems[e.sts[i].HandleClient].Value = e.sts[i];
                    OnReceivedNewData();   // raise event 1
                  }
         }
 protected virtual void OnReceivedNewData()
        {
            _EvenArgs.ReadItems = _ReadItems;
            RecivedNewData?.Invoke(this, _EvenArgs);
        }
    }

Callback DataChangeHandler podnosi event 1 OnReceiveNewData, kiedy nowe dane przyjda z OPC
Dane w _EvenArgs sa wysylane do DataMgr.

 public class DataMgr
    {
 public void OnReceivedRequestFromPlc(object o, ReadDataEventArg e)
        {
            ActualData.Id = ++_Id;
            if (_Id > 99999) _Id = 0;
            ActualData.PlcRequest = e.ReadItems[_PlcIndex].Value.DataValue.ToString();
            _LogMsg(true, "ID=" + ActualData.Id.ToString("00000") + " PLC Request: "+ ActualData.PlcRequest);
            OnReceivedNewRequest();  //** raise event 2**
        }

        protected virtual void OnReceivedNewRequest()
        {
            _PlcRequestEventArg.Request = ActualData.PlcRequest;
            RecivedPlcRequest?.Invoke(this, _PlcRequestEventArg);
        }

   } 

Jak DataMgr odbierze dane to podnosi event 2 OnReceivedNewRequest, ktory przekazuje dane do SQLInterface

public class SQLInterface
    {
public void OnReceivedSearchCommand(object o, SearchCommnadEventArg e)
        {
            _DataRequest = e.Request.Trim();
            LogMsg(_LogProcessName, _ExtDiag,"Received new search command: " + _DataRequest);
            if (_SearchDataThread != null) { while (_SearchDataThread.IsAlive) { } }
            _SearchDataThread = new Thread(new ThreadStart(GetDataFromDB));
            _SearchDataThread.IsBackground = true;
            _SearchDataThread.Priority = ThreadPriority.Lowest;
            _SearchDataThread.Start();
        }
}

Jak SQL interface odbierze dane z DataMgr, to uruchamia nowy watek _SerchDataThread, ktory wyszukuje dane w DB

public void GetDataFromDB()
        {
            //...
                try
                {
                    if (reader.Read())
                    {
                        if (!reader.IsDBNull(0))
                        {
                            _DataFoundEvenArg.Data = ((string)reader["Data"]).Trim();
                            LogMsg(_LogProcessName, _ExtDiag, "SQL: Data found: " + _DataFoundEvenArg.Data);
                           OnDataFound();  // raise event 3
                        }
                    }
                    else
                    {
                        _DataFoundEvenArg.Data = "99999999999999999999999999999";
                        LogMsg(_LogProcessName, _ExtDiag, "Data not exist for:  " + _cmdRead.Parameters["@PatternData"].Value);
                    }
                    
                }
                catch (Exception ex)
                {
                    LogMsg("EXCE", true, "Read data: " + _DataRequest + " from DB failed: " + ex.Message);
                }
                finally
                {
                    // Always call Close when done reading.
                    reader.Close();
                  }
            }
        

jak znajdzie dane to podnosi event 3 OnDataFound() ktory przekazuje dane do DataMgr, ten podnosi event 4 ktory przekazuje dane do wyslania przez OPC.

Z grubsza przeplyw danych jest nastepujacy:
OPC callback podnosi event 1 - > DataMgr odbiera dane, podnosi event 2 -> SQL odbiera dane, uruchamia nowy watek, watek podnosi event 3 -> DataMgr odbiera dane podnosi event 4 -> OPC odbiera dane za pomoca RecData().

Teraz gdzie jest problem.
W RecData(0 mialem blad. Blad zostal wychwycone przez exception w SQL thread.

catch (Exception ex)
                {
                    LogMsg("EXCE", true, "Read data: " + _DataRequest + " from DB failed: " + ex.Message);
                }

oznacza to ze watek z SQL jest "trzymany" az do zakonczenia RecData(). Na podobnej zasadzie spodziewam sie ze callback z OPC ( z pierwszego kroku) jest "trzymany" az do zakonczenia ( w tym wypadku do wystartowanie watku _SearchDataThread) , czego chcialem uniknac ;(.

Co zrobic zeby "zwalniac klasy" bezposrednio po podniesieniu eventu?
Czy jedynym rozwiazaniem jest w wywolanie nowego watku w subscrypcji, a dopiero ten watek podniesie nastepny event? Tak jak to zrobilem w SQLInterface (chociaz z innych przyczyn ;))

PS. Podnoszenie event 3 przenislem do finally

finally
                {
                    // Always call Close when done reading.
                    reader.Close();
                     OnDataFound();  // raise event 3

                  }

ale to rozwiazuje chyba tylko wywolanie dobrego exception.

0

przestawiles kod, wspominasz o jakims recdata a w ogole go nie ma w kodzie ;o

poki co nie jestem w stanie zrozumiec Twojego problemu. Wydaje mi sie ze to klasyczny problem XY http://mywiki.wooledge.org/XyProblem

oznacza to ze watek z SQL jest "trzymany" az do zakonczenia RecData()
nie wiem skad takie wnioski. Gdy zlapales wyjatek jedyne co robisz to

LogMsg
Wiec tylko to moze "trzymac" Ci wyjatek, ale zapewne tego nie robi.

Co zrobic zeby "zwalniac klasy" bezposrednio po podniesieniu eventu?
wytlumacz to zwalnianie. Bo czytam 3 raz i nadal nie rozumiem

Napisz tak

  1. Jaki masz teraz flow oraz to jak bys chcial by bylo. W takiej formie (na przyklad)
    Obecnie: Event -> Przetwarza event -> Rzuca wyjatek -> Wyjatek wisi
    Chce: Event -> Przetwarza event -> Rzuca wyjatek -> Wyjatek obsluzony
0

Ok, teraz juz bez kodu. Kod w pierwszym poscie to tylko czesc calosci dla zobrazowania problemu.

  1. Zewnetrzna biblioteka czyta dane z OPC server.
  2. W OPCInterface (moja klasa) mam callbacka z 1.
  3. Callback z 2. podnosi event (nazwijmy go event1)
  4. W DataMgr (moja klasa) wykonuje sie subscrybcja przypisana do event1 (nazwijmy ja sub1)
  5. W sub1 podnoszony jest event (nazwijmy go event2)
  6. W SQLInterface wykonuje sie subskrypcja przypisana do event2 (sub2)
  7. Sub2 rozpoczyna nowy watek thread1.
    I tu mniej konczy sie pierwszy data flow:

Callback -> event1 -> sub1 -> event2-> sub2 -> start thread1

  1. Po zakonczeniu wyszukiwania danych Thread1 podnosi event3
  2. W DataMgr wykonuje sie subskrybcja (sub3) przypisana do event3
  3. W sub3 podnoszony jest event4
  4. W OPCInterface wykonuje swie subscrypcja (sub4) przypisana do event4
  5. Sub4 wywolywana jest metoda RecData()
    czyl drugi data flow:

thread1 -> event3 -> sub3 -> event4 -> sub4-> RecData()
I wszystko dzialalo I dziala dobrze :), gdyby nie przypadek... ;)

W RecData() zrobilem blad (banalny, zmienna ==null), exception zostal wylapany w thread1 (to tez byl blad)
ponizej pokazane jest gdzie byl podnoszony event3 i gdzie zostal zlapany wyjatek

try
                {
                    if (reader.Read())
                    {
                        if (!reader.IsDBNull(0))
                        {
                            _DataFoundEvenArg.Data = ((string)reader["Data"]).Trim();
                            LogMsg(_LogProcessName, _ExtDiag, "SQL: Data found: " + _DataFoundEvenArg.Data);
                           OnDataFound();  // tu jest raise event 3
                        }
                    }
                    else
                    {
                        _DataFoundEvenArg.Data = "99999999999999999999999999999";
                        LogMsg(_LogProcessName, _ExtDiag, "Data not exist for:  " + _cmdRead.Parameters["@PatternData"].Value);
                    }
 
                }
                catch (Exception ex)
                {
                    LogMsg("EXCE", true, "Read data: " + _DataRequest + " from DB failed: " + ex.Message);  // tu zostal zlapany wyjatek
                }
                finally
                {
                    // Always call Close when done reading.
                    reader.Close();
                  }

Teraz wnioski (ktore moze nie sa sluszne):
Thread1 konczy sie dopiero po wykonaniu sub4
I drugi wniosek dotyczacy 1 data flow:
Callback zakonczy sie w momecie zakonczenia sub2.

Czyli dziala tak:
Callback -> event1 -> sub1 -> event2-> sub2 -> start thread1 (callbacka wisi az do start thread1)
thread1 -> event3 -> sub3 -> event4 -> sub4-> RecData() (thread1 wisi az do RecData())

Oczekiwania:
Callback -> event1 -> sub1 (koniec callbacka)
Event2 -> sub2 -> start Thread1 (koniec)
Thread1 -> Event3 -> sub3 (koniec thread1)
Event4 ->sub4 (RecData()) (koniec);

Rozwiazanie ktore zadziala:

Callback -> event1 -> sub1 -> start thread1 (koniec callback)
Thread1 ->event2-> sub2 -> start thread2 (koniec thread1)
Thread2 ->event3->sub3 -> start thread3 (kkoniec thread2)
Thread3 ->event4->sub4 -> RecData (koniec thread3)

Pytanie: Jaki jest lepszy sposob?

0

Znalazlem wytlumaczenie na stackoverflow.

The thread of execution doesn't return from the raising of the event until the event handlers are finished. https://stackoverflow.com/questions/6862259/deadlock-at-rasing-events-in-net

Wynika ze wnioski wyciagniete sa sluszne ;(. Subskrybcje eventu wykonywane sa w watku podnoszenia eventu (albo przynajmniej watek sie zatrymuje w tym miejscu) az do zakonczenia wszytkich subskrypcji
Czy istnieje jakies lepsze rozwiazanie oprocz startowania nowych watkow w kazdej subscrybcji?

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