Newsgroup: it.comp.lang.delphi
Titolo: Problema accesso concorrente a DB

Ciao!
Ho un DB Access al quale accedo tramite diversi threads. Ogni thread
accede al DB mediante una TCriticalSection, per evitare problemi
nell'accesso concorrente. Tuttavia si manifesta un problema: il
programma termina e si chiude da solo.
Il codice che ho usato è questo:
criticDB->Acquire();
try
{
        if (frmMain->CheckBoxDB->Checked)
        {
                if (!frmMain->KADaoDB->Connected)
                        frmMain->KADaoDB->Open();
                frmMain->KADaoTableDB->Insert();
                try
                {
frmMain->KADaoTableDB->FieldByName("campo1")->AsString = str;
                        frmMain->KADaoTableDB->Post();
                }
                catch(...)
                {
                        frmMain->KADaoTableDB->Delete();
                }
                frmMain->LabelDB->Caption =
frmMain->KADaoTableDB->RecordCount;
        }
}
__finally
{
        criticDB->Release();
}
Un accesso concorrente al DB Access, simile a questo ma funzionante, lo
avevo realizzato su un'altra applicazione, nella quale però non facevo
uso delle TCriticalSection ma del metodo Synchronize()...
Ho provato a sostituire la TCriticalSection col Synchronize(), ed anche
in questo caso la situazione non migliora: il programma non esegue il
codice dentro il Synchronize(). Quest'ultima anomalia forse è dovuta
al fatto che non posso usare nella stessa applicazione o nello stesso
thread sia Synchronize() che TCriticalSection.
Qualche consiglio o idea?? Grazie!!
Ciao

thread sia Synchronize() che TCriticalSection.
Non č cosě.

Andrea, capisco che sia meglio usare un'altro DB (tipo Firebird), ma al
momento non posso e dovrei far funzionare questa applicazione col suo
DB Access...
Ti garantisco che ho già realizzato una applicazione che tramite
diversi threads accede ad un DB Access. Però avevo usato il metodo
Synchronize(), non le TCriticalSection.
Utilizzando il codice che ho postato sopra, stranamente il programma
termina e si chiude da solo quando vado a visualizzare il form su cui
si trova la KADaoDBGrid del database... come mai cosa c'è che non va??
come posso fare?
Grazie mille! Ciao!

Credo di aver risolto, era una questione di mettere anche l'Insert()
dentro il blocco try-catch...
try
{
        frmMain->KADaoTableDBInvNon->Insert();
        frmMain->KADaoTableDBInvNon->FieldByName("Email")->AsString =
IdMessage1->Recipients->EMailAddresses;
        frmMain->KADaoTableDBInvNon->Post();
}
catch(...)
{
}

Verificando bene il funzionamento del suddetto codice mi sono accorto
purtroppo di una cosa. Adesso, anche se sembra non esserci alcun
errore, ancora non si ha il funzionamento corretto: viene inserito un
solo record, e tutti gli altri non vengono inseriti.
Non capisco perchè... cosa potrei fare??

Synchronize(), non le TCriticalSection.
Di fondo, hai usato comunque le cs, visto che Synchronize usa proprio
uan di esse.

Se qualcuno ha un'idea di come poter risolvere questo problema vi prego
di rispondere, ne sarei molto grato
Grazie mille! Ciao!

Non ho capito una cosa
Tu fai: insert, assegni i parametri, post
Se invece fallisci fai una delete, ma delete di cosa? Seil record non l'hai 
inserito perché sei andato in eccezzione, cosa stai cancellando? 

Quel codice era sbagliato, quello che non genera errori è il secondo,
cioè questo:
try
{
        frmMain->KADaoTableDB->Insert();
        frmMain->KADaoTableDB->FieldByName("campo1")->AsString = str;
        frmMain->KADaoTableDB->Post();
}
catch(...)
{
}
Purtroppo però neanche con questo codice ho il funzionamento corretto:
non si genera alcun errore ma viene inserito solo un record, gli altri
non vengono mai inseriti...

di rispondere, ne sarei molto grato
Il bello è che non hai neanche detto qual è questo problema ("il
programma si chiude da solo" non è l'esplicitazione di un problema,
perché mi fa capire che neanche sei andato in debug).

 non vengono mai inseriti...
E in debug che succede?
Mi apre di capire che gli altri thread provano a fare la insert ma dove si 
fermano?

Andrea,
forse non hai letto bene tutti i miei post. Ho modificato il codice ed
ora il problema non è più la terminazione anomala del programma, ma
che viene inserito solo un record nel DB mentre gli altri non vengono
mai inseriti

Scusa ma tu il post a cui hai fatto il reply l'hai letto?
Dov'č che ho scritto "programma si spegne"? 

Andrea,
Fai attenzione, per cortesia, a come imposti le tue risposte.  Non è a
me che stai rispondendo.

 Mi apre di capire che gli altri thread provano a fare la insert ma dove si fermano?
Risposta: testando i threads ho notato che viene eseguito sia il codice
che si trova prima della parte riguardante l'inserimento nel DB, sia
quello che si trova dopo. Perciò deduco che non c'è alcun blocco
nell'esecuzione, e che le eventuali eccezioni lanciate durante
l'inserimento nel DB sono correttamente catturate dal blocco
catch(...).
Non riesco ancora a spiegarmi perchè viene inserito un solo record...
Ho fatto un test per verificare lo stato del DB dopo la parte di codice
riguardante l'inserimento ed ho notato che il DB rimane nello stato di
Insert dsInsert.
Avete qualche idea o suggerimento?
Grazie dell'attenzione!

 catch(...).
Quindi mi pare di capire che riesci ad entrare ed uscire dalla sezione 
critica
Mo quello che vorrei capire č se il catch(...) č scritto cosě solo per farci 
capire o se davero ignori l'eventuale eccezione
Probabilmente hai impostato un campo che non puň essere null oppure non puň 
essere duplicato
Forse apri una transazione che poi non chiudi (probabilmente i componenti 
che usi aprono esplicitamente una transazione e quando ai nel catch non la 
chiudi)

Può darsi che quello che dici tu sia corretto Edmondo... per maggior
chiarezza riporto qui di seguito tutto il codice che ho usato:
criticDB->Acquire();
try
{
        if (!frmMain->KADaoDB->Connected)
        	frmMain->KADaoDB->Open();
        if (!frmMain->KADaoTableDB->Active)
        	frmMain->KADaoTableDB->Open();
        try
        {
                frmMain->KADaoTableDB->Insert();
                frmMain->KADaoTableDB->FieldByName("campo1")->AsString
= str;
                frmMain->KADaoTableDB->Post();
        }
        catch(...)
        {
        }
        frmMain->LabelDBInvNon->Caption =
frmMain->KADaoTableDB->RecordCount;
}
__finally
{
        criticDB->Release();
}
La chiave del DB è proprio il campo "campo1". Ma comunque viene
inserito un solo record anche se tento di inserire elementi che non
sono duplicati, quindi questo comportamento non dovrebbe essere dovuto
al fatto che tento di duplicare la chiave...
Come dici tu Edmondo, potrebbe forse essere che apro una transazione
che poi non chiudo... guardando il codice che ne dici??
Grazie mille!

> ... guardando il codice che ne dici??
Che nascondere una possibile eccezzione è sbagliato
Tu sai ceh il blocco try potrebbe generare eccezzione, lo metti in try catch 
ma se gnera eccezzione te ne freghi
Secondo me il problema è proprio lì
Fai la Insert e il db va in dsInsert come dicevi poco fa, poi succde 
qualcosa e va in catch ma tu non sai perché e non fai né un post né alcunché
Il mutex si libera e la volta successiva succede il papocchio
Mo perché la prima volta funzioni non saprei ma sapere cosa c'è scritto in 
quel catch... 

Dentro il catch(...) non c'è scritto niente... credevo fosse meglio
non mettere niente, in modo da evitare problemi in caso di eccezioni
lanciate in caso di doppioni... ripeto che questo stesso codice
funziona quando lo lancio nel thread principale, cioè dal Form. Però
potrebbe essere che quello che non mi crea problemi in una logica senza
threads me li crei invece con i threads. Quindi potrebbe essere che in
questo caso dovrei fare qualcosa nel catch(...) invece di non far
niente. Cosa? Non saprei...
Ho provato con il seguente codice nel catch, ma il risultato è che
stavolta viene inserito sempre un nuovo record, il quale viene poi
immediatamente eliminato:
catch(...)
{
        frmMain->KADaoTableDB->Cancel();
}
Che dovrei fare?

Mi pare chiaro che vai in eccezione
Quello ceh sto provando a dire è: guarda che eccezione è
Devi fare
catch (Exception &E)
    {
       E.Message.c_str() <- qui c'è scritto che cavolo è successo
    } 

L'errore che salta fuori è: EAccessViolation

Già si comincia a ragionare Così abbiamo escluso che il problema venga dal 
db
Mo questo è il classico errore di quando accedi ad uno spazio di memoria non 
allocato o di quando ci sono accessi concorrenti mal gestiti
La variabile str da dove viene? Chi la scrive? È condivisa?
Ci sono altre parti nel programma in cui accedi all'oggetto frmMain->KADaoDB 

Andiamo per ordine:
1) La variabile str è una variabile dichiarata all'interno di
Execute(), ma il problema non dipende da questa. Infatti ottengo lo
stesso errore rimuovendola dall'assegnazione.
2) str è scritta all'inizio di Execute tramite una TCriticalSection:
criticS->Acquire();
try
{
     str=frmMain->CheckListBox->Items->Strings[i];
}
__finally
{
     criticS->Release();
}
3) str non è una variabile condivisa ma, come ho detto prima, è
dichiarata all'interno di Execute, quindi il suo scopo è limitato ad
Execute
4) Non ci sono altre parti nel thread in cui accedo all'oggetto
frmMain->KADaoDB, ma ovviamente ci sono nel Form

Mi sono accorto di una cosa che potrebbe essere importante.
Se sul thread principale (il Form) utilizzo per accedere al DB lo
stesso codice dei threads secondari, vengono aggiunti i record ma se ci
sono dei duplicati il ciclo di inserimento si blocca. Per maggior
chiarezza scrivo il codice:
Così il thread principale funziona correttamente, anche con i
duplicati:
KADaoTableDB->Insert();
try
{
        KADaoTableDB->FieldByName("campo1")->AsString = str;
        KADaoTableDB->Post();
}
catch(...)
{
        KADaoTableDB->Delete();
}
Così invece si blocca il ciclo di inserimento se ci sono dei
duplicati:
try
{
        KADaoTableDB->Insert();
        KADaoTableDB->FieldByName("campo1")->AsString = str;
        KADaoTableDB->Post();
}
catch(...)
{
}

Scusa se insisto ma...
Queste CriticalSection mica le crei per ogni thread? Sei sicuro di averne 
create una sola istanza comune a tutti?
Io immagino una cosa tipo: ho un datamodule, come oggetto del dm ho una 
criticalsection e scrivo una funzione pubblica cui passo per valore delle 
variabili (assicurandomi che mentre le passo siano a loro volta lockate)
Dimmi che fai così altrmenti spiegami meglio chi crea cosa, quante istanze 
ci sono e di cosa, dove dishiari le variabili...
E poi in merito al post successivo (dove spieghi che succede nel thread 
principale) continui a silenziare le ecezzioni
Dici che in uno dei due modi non funziona ma qual'è l'eccezzione che 
solleva?
Secondo me usi male il meccanismo del try catch
Non serve a silenziare le eccezzioni e a non farle vedere all'utente ma ad 
intervenire opportunamente in caso di problema
Come fai tu non servono a niente
Cosa cambia se ho un'eccezzione ceh viene vista dall'utente o se la catturo 
e la silenzio? In entrambi i casi ho avuto un problema e non vi ho posto 
rimedio. Forse farla vedere all'utente sarebbe meglio; almeno quello ti 
chiama e ti dice che non funziona, almeno così lo sapresti 

Ti spiego meglio i dettagli di come è costruita l'applicazione:
- il Form frmMain
Questo è il form sul quale è ovviamente eseguito il thread
principale, e sul quale non ho alcun problema negli inserimenti nel DB.
Il frmMain chiama il ThreadDBMain con questo codice:
thrDBMain = new ThreadDBMain(_parametri qui_);
- il ThreadDBMain
Questo è il thread che uso per far partire i vari threads secondari,
nel quale dichiaro anche le TCriticalSection
VettThreadDB[i] = new ThreadDB(_parametri qui_);
VettThreadDB[i]->Resume();
- il ThreadDB
Questo è il thread nel quale accedo al DB e col quale sto avendo i
problemi prima menzionati. In questo thread le TCriticalSection le
definisco come extern, così:
extern TCriticalSection *criticDB;
Se servono altre informazioni potrei preparare una versione case-study
del programma, con tutti i sorgenti necessari
Grazie per l'attenzione!

 extern TCriticalSection *criticDB;
Non č piů bello passare il puntatore alla TCriticalSection come parametro 
del costruttore? A me piace di piů, č piů leggibile 

Rispondo scrivendo il codice, per chiarire ogni dubbio sulle
TCriticalSection:
//In ThreadDBMain
//qui vengono solo dichiarate e non utilizzate
TCriticalSection *criticDB1 = new TCriticalSection();
TCriticalSection *criticDB2 = new TCriticalSection();
TCriticalSection *criticDB3 = new TCriticalSection();
TCriticalSection *criticDB4 = new TCriticalSection();
//In ThreadDB
//qui vengono richiamate e utilizzate
extern TCriticalSection *criticDB1;
extern TCriticalSection *criticDB2;
extern TCriticalSection *criticDB3;
extern TCriticalSection *criticDB4;
...
criticDB1->Acquire();
...
Per essere ancora più chiaro metto online tutto il codice del thread
ThreadDB, visualizzabile qui:
http://phpfi.com/110377
Spero nel vostro aiuto! :-)

Adesso è tutto chiaro sui semafori
Evidenti errori non ne vedo solo due cose mi chiedo:
1) Accedi con un ciclo for agli elementi di una StringList (?) ma sei sicuro 
di non "uscire con i piedi da fuori"?
2) In una sezione critica accedi a un sacco di robba presente sulla form 
principale, sicuro che nessun'altro acceda alla form? 

 principale, sicuro che nessun'altro acceda alla form?
Si ne sono abbastanza sicuro, perchè il Form in questo caso non fa
alcuna operazione oltre che avviare i threads secondari. Ecco il codice
che esegue il Form:
thrDBMain = new ThreadDBMain(_parametri qui_);
cmdPausa->Visible = true;
Application->ProcessMessages();
if (thrDBMain->WaitFor() )
{
}
delete thrDBMain;
Comunque vorrei mettere in evidenza che ho problemi solo col DB, non
con gli altri elementi del Form. Infatti gli stessi elementi che dovrei
aggiungere al DB, come puoi vedere sul codice che ho postato, riesco ad
aggiungerli correttamente sulla CheckListBoxNon col codice:
frmMain->CheckListBoxNon->Items->Add(str);
E' strano. Questo errore EAccessViolation dovrebbe essere dovuto a
qualcosa riguardante il DB, non agli altri componenti perchè
funzionano tutti bene. Ma al DB accedo solo tramite la
TCriticalSection, quindi non dovrei avere problemi di concorrenza...
non capisco cosa succede...
Come si può risolvere questo problema?? Forse invece di usare la
KADaoTableDB che è sul Form dovrei crearne una in ogni thread??
Grazie mille e buona domenica!!