Et voilà, je viens d'y passer une journée (encore …) et non, Sql Server CE, n'est PAS multi thread safe…
Alors voilà soyons clair :
|
Feature
|
SQL CE
|
|
Size of total deployment
|
1,834 KB
|
|
Number of concurrent connections
|
256 |
|
Concurrent process connections
|
1 |
|
Database Size Limit
|
4GB |
|
Max CPUs Supported
|
1 |
Alors la conclusion est consternante, mais :
- Oui, vous pouvez ouvrir plein de connections simultannément
- Non, vous ne pouvez pas ouvrir 2 connections sur 2 threads séparés..
Maintenant, un exemple que je viens de faire.
Une petite application où je teste 3 cas. Dans chacun des cas, je fais 1 appels sur 2 services différents. Chaque service me renvoyant des données provenant d'une base de donnée Sql Server CE.
les 3 cas que je veux tester :
- Apel des 2 services en synchrone, histoire de vérifier que ça marche dans un cas classique mono thread
- Appel de mes 2 services par WCF, en monde Asynchrone (via les proxys générés et leurs appels asynchrones générés)
- Appel de mes 2 services directement, et utilisation de BackgroundWorkers
L'application est architecturée de manière classique.
- Une UI
- Un Service WCF
- Un projet Entité, partagé par le service et l'UI
Je fais un peu de WCF, mes services sont exposés et je génère les proxys des services via un "Add Service Reference" :
Note : Je demande à générer les appels asynchrones, sinon aucun intéret de tester le multi-threading
Petit Hack dans mon architecture : Pour le 3eme cas, je référence directement mon assembly service dans mon projet UI (Rhoooo c'est pas bien; mais c'est pour l'exemple
)
Pour information, j'ai utilisé Linq To Sql pour générer le dbml dans le projet Entités, mais pour être absolument sûr du résultat, j'utilise une bonne vielle connection ADO.NET et je génère les commandes à la main.
Cas 1 : WCF Synchrone
Je crée mes 2 services (mes proxys) :
// For Exemple 1 and 2 : use of WCF Services
public ClientProxy.ClientServicesClient ClientServices { get; set; }
public ParameterProxy.ParameterServicesClient ParameterServices{ get; set; }
Je les instancie au démarrage :
ClientServices = new SqlServerCE_WCFAsync.ClientProxy.ClientServicesClient();
ParameterServices = new SqlServerCE_WCFAsync.ParameterProxy.ParameterServicesClient();
J'appel mes 2 services :
this.dataGridView1.DataSource = ClientServices.GetClients2();
var p = ParameterServices.GetParameter("AddressMail");
if (p != null)
{
label1.Text = p.Value;
}
Bon, ben ça, ça fonctionne…
Continuons..
Cas 2 : WCF Asynchrone
Mes 2 services sont créés (voir Cas 1)
Je configure le service Client, pour qu'il utilise son proxy de manière asynchrone :
ClientServices.GetClients2Completed += ClientServices_GetClients2Completed;
je crée la méthode Completed :
void ClientServices_GetClients2Completed(object sender,
SqlServerCE_WCFAsync.ClientProxy.GetClients2CompletedEventArgs e)
{
this.dataGridView1.DataSource = e.Result;
}
Et je tente d'appeller ce service en asynchrone, alors que j'appel le service Parameter en synchrone lui :
ClientServices.GetClients2Async();
var p = ParameterServices.GetParameter2("AddressMail");
if (p != null)
{
label1.Text = p.Value;
}
Et là ça se vautre lamentablement avec une exception qui ferait frémir un mamouth congelé :
"
A first chance exception of type 'System.AccessViolationException' occurred in System.Data.SqlServerCe.dll
Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
"
Cas 3 : Utilisation de BackGroundWorker
Ici, je me passe de WCF (et donc je référence directement l'assembly service dans mon projet UI)
et je tente d'utiliser un BackGroundWorker sur le service ClientServices et je fais un appel direct sur le service ParameterServices
Configuration du BackGroundWorker :
bgw1 = new BackgroundWorker();
bgw1.DoWork += new DoWorkEventHandler(bgw1_DoWork);
bgw1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgw1_RunWorkerCompleted);
Configuration du travail à effectuer :
void bgw1_DoWork(object sender, DoWorkEventArgs e)
{
SqlServerCE_WCFServices.ClientServices service = new SqlServerCE_WCFServices.ClientServices();
e.Result = service.GetClients2();
}
Configuration du completed du BackGround Worker :
void bgw1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
Trace.WriteLine("Erreur in User Interface in GetClients. Exception : " + e.Error.Message);
if (e.Cancelled)
Trace.WriteLine("Action cancelled in User Interface in GetClients");
this.dataGridView1.DataSource = e.Result as List<Client>;
}
Appel :
bgw1.RunWorkerAsync();
SqlServerCE_WCFServices.ParameterServices service = new SqlServerCE_WCFServices.ParameterServices();
var p = service.GetParameter2("AddressMail");
if (p != null)
{
label1.Text = p.Value;
}
Et là, c'est le drame …
"
A first chance exception of type 'System.AccessViolationException' occurred in System.Data.SqlServerCe.dll
Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
"
Conclusion
Alors voilà, conclusion:
- Oui, utilisez un BackGround Worker pour faire des appels asynchrone et rendre votre application moins freezée et plus agréable
- Non, n'utilisez pas d'appel asynchrone sur plusieurs threads…
Et là, je demande, comment faire avec WCF ?? Le monsieur génère un Thread par Service Proxy… c'est balôt !
Et je demande encore, comment faire une opération (de maintenance, ou de synchronisation) en arrière plan dans mon application desktop sous SQL Server CE ? Je demande à mon utilisateur d'être sympa et de pas faire de requêtes en attendant ??
Je laisse le code à disposition si le coeur vous tente

SQL CE