Autor Téma: Nenápadná havárie vlákna?  (Přečteno 2679 krát)

Offline Morrison

  • Hrdina
  • ****
  • Příspěvků: 291
  • Karma: 12
    • Verze Delphi: D5, XE2, 10.4
Nenápadná havárie vlákna?
« kdy: 05-07-2012, 16:38:27 »
Setkal jste se někdo s tím, že vlákno "zhavaruje" bez jediné vyjímky? V aplikaci (přehrávání MJPEG streamu z IP kamery) mám dvě vlastní vlákna - v jednom běží nekonečná smyčka s GET požadavkem, druhé zpracovává a zobrazuje přijatá data. Nepředával jsem si mezi nimi data metodou vlákna Synchronize(), ale za použití TCriticalSection - měl jsem totiž za to, že je to ekvivalent k Synchronize. Nicméně problém se projevoval tak, že za určitých okolností (pohyb formuláře po ploše, přepnutí do jiné aplikace a zpět a pod.) se mi OBĚ vlákna sama ukončila (mám tam FreeOnTerminate := true). Prostě mi to bez jakékoliv hlášky skočilo do destruktoru vlákna (předpokládám, že se zavolala původní Terminate(), protože do "mojí" Terminate() mi to nevlezlo dříve, než do destruktoru).
Čili abych to zesumarizoval : dvě běžící vlákna se při předávání dat obě zhroutila, ale BEZ A/V nebo jiné vyjímky typické pro nesynchronizovanou výměnu dat. Pro úplnost, checkingy mám samozřejmě zapnuté všechny.
Setkal jste se s tím někdo? Je to normální? Po přesunutí výměny dat do Synchronize() problém, zdá se, zmizel. Pro informaci, ta výměna dat spočívala v zápisu a čtení AnsiStringu.
nil

Offline Mi.Chal.

  • Guru
  • *****
  • Příspěvků: 576
  • Karma: 25
Re:Nenápadná havárie vlákna?
« Odpověď #1 kdy: 05-07-2012, 17:44:53 »
máš všude odchytávané výjimky? Chce to odchytávat a logovat do souboru.

Synchronize není to samé jako TCriticalSection - Synchronize jenom říká, že se má něco volat v UI threadu. Důsledkem je, že to bude synchronizované, protože ti ten kód nebudou vykonávat dva thready najednou. TCriticalSection je jeden ze způsobů synchronizace více vláken, kdy se jeden kód nemá vykonávat ve více vláknech - v tom je to podobné, ale kód poběží v původním threadu, zatímco Synchronize ho bude pouštět v UI threadu.

Samotná výměna dat vadit nejspíš nebude. Podle toho co píšeš to ale vypadá, že se v rámci obsluhy snažíš někde hrabat do UI (stačí třeba v setteru property na formuláři nastavovat nějakou vlastnost nějaké UI komponentě), to ovšem nejde. Některá vývojová prostředí ti hodí výjimku, Delphi myslím nic rozumného neházely, ale moc to nefungovalo. Jak ses mohl sám přesvědčit.

Offline Morrison

  • Hrdina
  • ****
  • Příspěvků: 291
  • Karma: 12
    • Verze Delphi: D5, XE2, 10.4
Re:Nenápadná havárie vlákna?
« Odpověď #2 kdy: 05-07-2012, 17:56:15 »
máš všude odchytávané výjimky? Chce to odchytávat a logovat do souboru.
No zatím to neustále spouštím přímo v Delphi, takže, pokud nemám tragické mezery ve znalostech, tak bych řekl, že všechny vyjímky by mi mělo předat Delphi, nebo ne?
Citace
Synchronize není to samé jako TCriticalSection - Synchronize jenom říká, že se má něco volat v UI threadu. Důsledkem je, že to bude synchronizované, protože ti ten kód nebudou vykonávat dva thready najednou. TCriticalSection je jeden ze způsobů synchronizace více vláken, kdy se jeden kód nemá vykonávat ve více vláknech - v tom je to podobné, ale kód poběží v původním threadu, zatímco Synchronize ho bude pouštět v UI threadu.
Každé vlákno má svou vlastní CS, je to tak správně, nebo by měla sdílet jednu společnou?
Citace
Samotná výměna dat vadit nejspíš nebude. Podle toho co píšeš to ale vypadá, že se v rámci obsluhy snažíš někde hrabat do UI (stačí třeba v setteru property na formuláři nastavovat nějakou vlastnost nějaké UI komponentě), to ovšem nejde. Některá vývojová prostředí ti hodí výjimku, Delphi myslím nic rozumného neházely, ale moc to nefungovalo. Jak ses mohl sám přesvědčit.
Ano, hrabu do hlavního threadu odkazem na objekt druhého vlákna (např. Form1.PrijemVlakno), když mu nastavuji "probouzecí" event a pod. Když bych na druhé vlákno přistupoval třeba přes ukazatel, bylo by to OK?

Díky!
nil

Offline Mi.Chal.

  • Guru
  • *****
  • Příspěvků: 576
  • Karma: 25
Re:Nenápadná havárie vlákna?
« Odpověď #3 kdy: 05-07-2012, 18:18:43 »
No zatím to neustále spouštím přímo v Delphi, takže, pokud nemám tragické mezery ve znalostech, tak bych řekl, že všechny vyjímky by mi mělo předat Delphi, nebo ne?

To asi jo. Ale až to pustíš někde jinde samotný a spadne ti to, tak je lepší mít možnost si přečíst v logu, kde přesně byl problém.

Každé vlákno má svou vlastní CS, je to tak správně, nebo by měla sdílet jednu společnou?

kódy pracující se stejným chráněným prostředkem by tam měly dolézt přes stejnou CS. Pokud si uděláš dvě vlákna a budou pracovat třeba s nějakou globální proměnnou a každé si vyrobí svoji CS, tak to fungovat nebude.

Ano, hrabu do hlavního threadu odkazem na objekt druhého vlákna (např. Form1.PrijemVlakno), když mu nastavuji "probouzecí" event a pod. Když bych na druhé vlákno přistupoval třeba přes ukazatel, bylo by to OK?

to samo o sobě nevadí, problém je kdybys třeba nastavoval Form.Title nebo Form.Edit.Text. Co má být ten "probouzecí event"?

Offline Morrison

  • Hrdina
  • ****
  • Příspěvků: 291
  • Karma: 12
    • Verze Delphi: D5, XE2, 10.4
Re:Nenápadná havárie vlákna?
« Odpověď #4 kdy: 05-07-2012, 18:50:06 »
To asi jo. Ale až to pustíš někde jinde samotný a spadne ti to, tak je lepší mít možnost si přečíst v logu, kde přesně byl problém.
Rozhodně souhlas. V hlavní aplikaci, kam tenhle "kamerový" modul přijde připojit, samozřejmě mám vlastní exception handler s možností zápisu do souboru.

Citace
kódy pracující se stejným chráněným prostředkem by tam měly dolézt přes stejnou CS. Pokud si uděláš dvě vlákna a budou pracovat třeba s nějakou globální proměnnou a každé si vyrobí svoji CS, tak to fungovat nebude.
Dobře, mohl bych Tě tedy poprosit o stručné nastínění, jak řešit tenhle konkrétní případ s TCriticalSection namísto Synchronize()? Jsou to vlastně dvě vlákna producent-konzument. Producent vyprodukuje AnsiString. Jak ho mám nejlépe předat konzumentovi?
Citace
to samo o sobě nevadí, problém je kdybys třeba nastavoval Form.Title nebo Form.Edit.Text. Co má být ten "probouzecí event"?
To je obyč. LongWord, který si nastavím pomocí CreateEvent a vlákno na něj pak čeká ve WaitForMultipleObjects(). Takže do toho pak hrabu nějak takto :
Kód: Delphi [Vybrat]
  1. // toto je soucasti HTTPSend objektu, proto ten FOwner
  2. // HTTPSend patri producentu (PrijemVlakno)
  3. if FOwner <> nil then begin  
  4.   TPrijemThread(FOwner).FCS.Enter; // vlastni kriticka sekce tohoto vlakna
  5.   try
  6.     Form1.PrijemVlakno.PracAString := s;  // producent ma data "u sebe"
  7.     Win32Check(SetEvent(Form1.ZpracovaniVlakno.Eventy[zeNovaData]));  // signal pro konzumenta  
  8.   finally
  9.     TPrijemThread(FOwner).FCS.Leave;
  10.   end;
  11.  
Na straně konzumenta jsem musel dát načtení toho PracAString do Synchronize(). Když jsem to měl přes kritickou sekci, tak to blblo. Nebylo to tedy spíš proto, že jsem ve zpracovacím vlákně uzamykal jeho vlastní CS a nikoliv tu u druhého vlákna, ze kterého tahám ten AnsiString?

P.S. jak teď na to koukám, ten kód je docela "chaos", jednou používám TPrijemThread(FOwner). a o kus dál k tomu přistupuji přímo přes Form1.PrijemVlakno...  ::)
« Poslední změna: 05-07-2012, 19:05:46 od Morrison »
nil

Offline Mi.Chal.

  • Guru
  • *****
  • Příspěvků: 576
  • Karma: 25
Re:Nenápadná havárie vlákna?
« Odpověď #5 kdy: 05-07-2012, 19:05:02 »
To je obyč. LongWord, který si nastavím pomocí CreateEvent a vlákno na něj pak čeká ve WaitForMultipleObjects(). Takže do toho pak hrabu nějak takto :
Kód: Delphi [Vybrat]
  1. Win32Check(SetEvent(Form1.PrijemVlakno.Eventy[zeNovaData]));

No ale co děláš, když ten Wait skončí? Do UI teda nelezeš ani z jednoho threadu? Možná by nebylo od věci sem dát kódy těch threadů.

Jinak jak to má vypadat - třeba tak, že vlákno má criticalSection a property, která vrací/nastavuje data. Při čtení i zápisu bys měl použít tu CS a v rámci ní provést čtení/zápis dat.

Offline Morrison

  • Hrdina
  • ****
  • Příspěvků: 291
  • Karma: 12
    • Verze Delphi: D5, XE2, 10.4
Re:Nenápadná havárie vlákna?
« Odpověď #6 kdy: 05-07-2012, 19:09:18 »

No ale co děláš, když ten Wait skončí? Do UI teda nelezeš ani z jednoho threadu? Možná by nebylo od věci sem dát kódy těch threadů.

Ten WaitFor... je ve smyčce while not (Terminated or Application.Terminated).
Do UI samozřejmě zasahuji ze zpracovacího vlákna - zobrazuji načtený JPeG do TImage, ale tam mi přijde Synchronize() zcela na místě, to bych nechal. Nebo ne?

Citace
Jinak jak to má vypadat - třeba tak, že vlákno má criticalSection a property, která vrací/nastavuje data. Při čtení i zápisu bys měl použít tu CS a v rámci ní provést čtení/zápis dat.
Takže dejme tomu, že data má u sebe vlákno-producent. Producent při zápisu nastaví svou CS, ale co konzument při čtení? Ten má tedy nastavovat také producentovu CS?
« Poslední změna: 05-07-2012, 19:23:07 od Morrison »
nil

Offline Mi.Chal.

  • Guru
  • *****
  • Příspěvků: 576
  • Karma: 25
Re:Nenápadná havárie vlákna?
« Odpověď #7 kdy: 05-07-2012, 20:08:49 »
Oba thready mají použít stejnou instanci CS.

Dej sem kód těch threadů. Koukni na nějaký example, třeba http://edn.embarcadero.com/article/22411.

Offline Morrison

  • Hrdina
  • ****
  • Příspěvků: 291
  • Karma: 12
    • Verze Delphi: D5, XE2, 10.4
Re:Nenápadná havárie vlákna?
« Odpověď #8 kdy: 06-07-2012, 06:36:10 »
Díky moc za cenné rady! Kód bych sem eventuelně mohl dát, ale a)teď už to problémy nedělá a b)jej musím napřed trochu upravit, abych se nepropadl hanbou ;)
nil