I'm developing a C# application that needs to handle simultaneous printing on 4 Evolis Primacy 2 printers with Elyctis encoders. Each printer must write to card chips and print on cards completely independently without conflicts.
The current implementation uses Task.Run to process batches, but I'm experiencing resource conflicts, deadlocks, and memory issues when multiple printers run concurrently.
Main Batch Processing Method:
private async void BTNPERSOLOT_Click(object sender, EventArgs e)
{
BTNPERSOLOT.Enabled = false;
List<Personnalisation> cartesAImprimer = listeFiltreePerso
.Take(FenêtrePrincipale.nombreCarteToPrint)
.Select(c => (Personnalisation)c.Clone())
.ToList();
string nomImprimante = CB_Printerss.SelectedItem.ToString();
listeFiltreePerso = listeFiltreePerso.Skip(FenêtrePrincipale.nombreCarteToPrint).ToList();
bindingSource.DataSource = listeFiltreePerso;
InitialiserDataGridViewPersonnalisation(listeFiltreePerso);
_ = Task.Run(async () =>
{
try
{
Device device = Evolis.Evolis.GetDevices().FirstOrDefault(p =>
string.Equals(p.name, nomImprimante, StringComparison.OrdinalIgnoreCase));
string[] lecteursEnregistres = ws.getReadersByPrinter(nomImprimante);
string[] lecteursValides = await GetValidPCSCReaders(lecteursEnregistres);
await impressionSimultanee(cartesAImprimer, device, lecteursValides);
this.Invoke((MethodInvoker)delegate
{
MessageBox.Show(this,
$"Lot of {cartesAImprimer.Count} cards for printer {nomImprimante} completed.",
"Batch Done", MessageBoxButtons.OK, MessageBoxIcon.Information);
});
}
catch (Exception ex)
{
this.Invoke((MethodInvoker)delegate
{
MessageBox.Show(this,
$"Error in batch for printer {nomImprimante}:\n\n{ex.Message}",
"Batch Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
});
}
});
await Task.Delay(500);
BTNPERSOLOT.Enabled = true;
}
Simultaneous Printing Logic:
private async Task impressionSimultanee(List<Personnalisation> cartesAImprimer, Device imprimante, string[] lecteursImprim)
{
int successCount = 0;
List<string> lignesNonEcrites = new List<string>();
foreach (var carte in cartesAImprimer)
{
PrintTools printTool = null;
ConnexionResult resultatConnexionPuce = null;
try
{
printTool = new PrintTools();
resultatConnexionPuce = await PrintTools.connexionPuceAsync(imprimante, lecteursImprim);
if (!resultatConnexionPuce.IsSuccess)
{
throw new Exception("Chip connection failed.");
}
ModelResponse<bool> isWrite = PrintTools.ecriturePuce(carte, imprimante, lecteursImprim, resultatConnexionPuce.ConnectionObject);
if (!isWrite.status)
{
throw new Exception($"Chip writing failed: {isWrite.message}");
}
if (DateTime.TryParse(carte.dateNaissance, out DateTime dateN))
carte.dateNaissance = dateN.ToString("dd/MM/yyyy");
if (imprimante.model.ToString().Equals("Evolis_Primacy_2"))
{
Retour retour = await printTool.imprimerCarteEvolis(carte, imprimante.name);
if (!retour.IsSuccess)
{
throw new Exception("Graphic printing error!");
}
}
Personnalisation perso = await carteLocalRepository.GetPersonnalisationByNinLocalAsync(carte.nin);
if (perso != null)
{
perso.statutCarte = 2;
perso.numeroSerie = resultatConnexionPuce.NumeroSerie;
perso.login = User.login;
perso.dateDelivrance = DateTime.Now.ToString("yyyy-MM-dd");
perso.dateExpiration = DateTime.Now.AddYears(10).ToString("yyyy-MM-dd");
ResponseData reponsePersonnalisation = await ConnexionDB.updatePersonnalisation(perso);
if (reponsePersonnalisation != null && !reponsePersonnalisation.status)
{
lignesNonEcrites.Add($"{carte.prenom} {carte.nom} (DB Update Error: {reponsePersonnalisation.msg})");
}
}
successCount++;
}
catch (Exception ex)
{
lignesNonEcrites.Add($"{carte.prenom} {carte.nom} (Error: {ex.Message})");
PrintTools.execPrinterCmd("Se");
}
finally
{
if (printTool != null)
{
try { printTool = null; } catch { }
}
if (resultatConnexionPuce != null)
{
try { resultatConnexionPuce.ConnectionObject?.Disconnect(DISCONNECT.Leave); } catch { }
try { resultatConnexionPuce.m_iResMan?.ReleaseContext(); } catch { }
}
}
}
if (lignesNonEcrites.Count > 0)
{
string erreurs = string.Join("\n", lignesNonEcrites);
throw new Exception($"Batch completed with {lignesNonEcrites.Count} failure(s):\n{erreurs}");
}
}
Chip Connection Method:
public static async Task<ConnexionResult> connexionPuceAsync(Device choosenPrinter, string[] lecteursImprimante)
{
var result = new ConnexionResult { IsSuccess = false };
ISCardManager m_iResMan = null;
ISCardConnection m_iCard = null;
bool connected = false;
if (choosenPrinter.model.ToString().Equals("Evolis_Primacy_2"))
{
Connection co = null;
try
{
co = new Connection(choosenPrinter.name);
if (co.IsOpen())
{
string retour = co.SendCommand("Sis");
}
}
finally
{
co?.Close();
}
}
try
{
m_iResMan = new SCardManager();
m_iResMan.EstablishContext(SCOPE.System);
foreach (string lecteur in lecteursImprimante)
{
try
{
m_iCard = m_iResMan.CreateConnection(lecteur);
m_iCard.Connect(SHARE.Shared, PROTOCOL.T0orT1);
if (m_iCard.Connected)
{
connected = true;
break;
}
}
catch
{
try { m_iCard?.Disconnect(DISCONNECT.Leave); } catch { }
m_iCard = null;
}
}
if (!connected)
{
throw new Exception("No card found in printer readers");
}
ReadService readService = new ReadService(m_iCard);
ModelResponse<string> reponseNumeroSerie = readService.getSerailNumber();
if (string.IsNullOrEmpty(reponseNumeroSerie?.message))
{
throw new Exception("Cannot read card serial number");
}
result.NumeroSerie = reponseNumeroSerie.message;
bool isCardPresente = await carteLocalRepository.CheckCarteExistsByNumeroSerieLocalAsync(result.NumeroSerie);
if (!isCardPresente)
{
Webservice ws = new Webservice();
isCardPresente = ws.CheckCarteExistsByNumeroSerie(result.NumeroSerie);
}
if (!isCardPresente)
{
throw new Exception($"Card (No. {result.NumeroSerie}) not found in database!");
}
result.IsSuccess = true;
result.ConnectionObject = m_iCard;
result.m_iResMan = m_iResMan;
return result;
}
catch (Exception exc)
{
try { m_iCard?.Disconnect(DISCONNECT.Leave); } catch { }
try { m_iResMan?.ReleaseContext(); } catch { }
throw new Exception("Error during card reading/verification: " + exc.Message, exc);
}
}
Chip Writing Method:
public static ModelResponse<bool> ecriturePuce(Personnalisation personnalisation, Device choosenPrinter, string[] lecteursImprimante, ISCardConnection ConnectionObject)
{
ModelResponse<bool> valeurRetour = new ModelResponse<bool> { status = false, message = "Error!", code = 0 };
try
{
if (personnalisation?.visage != null)
{
string compressedBase64 = compressImage(personnalisation.visage);
if (Base64ToHex(personnalisation.visage).Length > 75534)
{
personnalisation.visage = compressedBase64;
}
}
if (personnalisation?.signature != null)
{
string compressedBase64 = compressSignature(personnalisation.signature);
if (Base64ToHex(personnalisation.signature).Length > 34000)
{
personnalisation.signature = compressedBase64;
}
}
if (!ConnectionObject.Connected)
{
throw new Exception("Writing impossible: No card found.");
}
if (validation(personnalisation))
{
WriteService writeService = new WriteService(ConnectionObject);
ModelResponse<bool> isInitialize = writeService.initialisationStructure();
if (isInitialize.status)
{
DG1File dg1 = new DG1File();
dg1.registrationNumber = personnalisation.numeroEnregistrement ?? " ";
dg1.pensionerNumber = personnalisation.numeroPensionnaire ?? " ";
dg1.phoneNumber = personnalisation.telephone ?? " ";
dg1.idNumber = personnalisation.nin ?? " ";
dg1.prenom = personnalisation.prenom ?? " ";
dg1.nom = personnalisation.nom ?? " ";
dg1.sexe = !string.IsNullOrEmpty(personnalisation.sexe) ? personnalisation.sexe[0].ToString() : " ";
dg1.prenomPere = personnalisation.prenomPere ?? " ";
dg1.prenomMere = personnalisation.prenomMere ?? " ";
dg1.nomMere = personnalisation.nomMere ?? " ";
dg1.dateNaissance = dateFormated(personnalisation.dateNaissance) ?? " ";
dg1.lieuNaissance = personnalisation.lieuNaissance ?? " ";
DG2File dg2 = new DG2File();
dg2.dateDelivrance = DateTime.Now.ToString("dd-MM-yyyy");
personnalisation.dateDelivrance = dg2.dateDelivrance;
dg2.dateExpiration = DateTime.Now.AddYears(10).ToString("dd-MM-yyyy");
personnalisation.dateExpiration = dg2.dateExpiration;
dg2.dateDerniereLecture = DateTime.Now.ToString("dd-MM-yyyy");
DG3File dg3 = new DG3File();
if (personnalisation.signature == null)
{
dg3.neSaitPasSigner = true;
dg2.tailleSignature = "0";
}
else
{
dg3.neSaitPasSigner = false;
dg3.signature = personnalisation.signature;
dg2.tailleSignature = Base64ToHex(personnalisation.signature).Length.ToString();
}
DG4File dg4 = new DG4File();
dg4.etatMainGauche = true;
dg4.etatMainDroite = true;
Finger[] fingers = new Finger[10];
for (int i = 1; i <= 10; i++)
{
Finger finger = new Finger();
finger.numeroDoigt = i;
string etat = GetFingerState(personnalisation, i);
string imageBase64 = GetFingerImage(personnalisation, i);
finger.etat = AsciiToHex(etat);
if (!string.IsNullOrEmpty(imageBase64))
{
try
{
Image img = Base64ToImage(imageBase64);
finger.imageEmpreinte = new Bitmap(img);
}
catch
{
// Handle bad finger image
}
}
fingers[i - 1] = finger;
}
dg4.fingers = fingers;
DG5File dg5 = new DG5File();
string visageBase64 = personnalisation.visage;
dg5.visage = visageBase64;
dg2.taillePhoto = Base64ToHex(visageBase64).Length.ToString();
DGFile dgs = new DGFile();
dgs.dg1 = dg1;
dgs.dg2 = dg2;
dgs.dg3 = dg3;
dgs.dg4 = dg4;
dgs.dg5 = dg5;
ModelResponse<DGFile> reponse = writeService.writeAllDG_version2(dgs);
valeurRetour = new ModelResponse<bool>
{
status = reponse.status,
message = "Message: " + reponse.message + " Code: " + reponse.code,
code = reponse.code
};
if (valeurRetour.status)
{
bool isrolled = enrolMunitiae(personnalisation, choosenPrinter, lecteursImprimante);
if (isrolled)
{
ModelResponse<string> reponseFinal = writeService.finalizeFileSystem();
}
}
}
}
return valeurRetour;
}
catch (Exception ex)
{
return new ModelResponse<bool> { status = false, message = ex.Message, code = -1 };
}
finally
{
if (Imprimante.Nom != null && Imprimante.Nom.Contains("Evolis Primacy 2"))
{
Connection co = new Connection();
if (co.IsOpen())
{
co.SendCommand("Sic");
}
}
}
}
Graphic Printing Method:
public async Task<Retour> imprimerCarteEvolis(Personnalisation personnalisation, string printer)
{
Retour retour = new Retour();
byte[] imageBytes = Convert.FromBase64String(personnalisation.visage);
Image photo = null;
using (MemoryStream ms = new MemoryStream(imageBytes))
{
photo = Image.FromStream(ms);
}
int largeurCarte = 1016;
int hauteurCarte = 648;
using (Bitmap rectoCarte = new Bitmap(largeurCarte, hauteurCarte, PixelFormat.Format24bppRgb))
using (Bitmap versoCarte = new Bitmap(largeurCarte, hauteurCarte, PixelFormat.Format24bppRgb))
using (Graphics graphiqueRecto = Graphics.FromImage(rectoCarte))
using (Graphics graphiqueVerso = Graphics.FromImage(versoCarte))
{
graphiqueRecto.FillRectangle(Brushes.White, 0, 0, largeurCarte, hauteurCarte);
graphiqueVerso.FillRectangle(Brushes.White, 0, 0, largeurCarte, hauteurCarte);
int margeGauche = 330;
int margeHaut = 200;
int espaceLigne = 30;
// Recto layout
graphiqueRecto.DrawString("Nom", new Font("Arial", 18, FontStyle.Regular), Brushes.Black, margeGauche, margeHaut);
graphiqueRecto.DrawString($"{SafeUpper(personnalisation.nom)}", new Font("Arial", 20, FontStyle.Bold), Brushes.Black, margeGauche, margeHaut + espaceLigne - 5);
graphiqueRecto.DrawString("Prénom", new Font("Arial", 18, FontStyle.Regular), Brushes.Black, margeGauche, margeHaut + espaceLigne * 2);
graphiqueRecto.DrawString($"{SafeUpper(personnalisation.prenom)}", new Font("Arial", 20, FontStyle.Bold), Brushes.Black, margeGauche, margeHaut + espaceLigne * 3 - 5);
graphiqueRecto.DrawString("Né(e) le", new Font("Arial", 18, FontStyle.Regular), Brushes.Black, margeGauche, margeHaut + espaceLigne * 4);
graphiqueRecto.DrawString($"{personnalisation.dateNaissance}", new Font("Arial", 20, FontStyle.Bold), Brushes.Black, margeGauche, margeHaut + espaceLigne * 5 - 5);
graphiqueRecto.DrawString("Numéro matricule", new Font("Arial", 18, FontStyle.Bold), Brushes.Black, margeGauche + 320, margeHaut + espaceLigne * 4);
graphiqueRecto.DrawString($"{personnalisation.nin}", new Font("Arial Black", 20, FontStyle.Bold), Brushes.Black, margeGauche + 320, margeHaut + espaceLigne * 5 - 5);
// Photo positioning
int positionPhotoX = 30;
int positionPhotoY = 200;
int largeurPhoto = 280;
int hauteurPhoto = 330;
graphiqueRecto.DrawImage(photo, positionPhotoX, positionPhotoY, largeurPhoto, hauteurPhoto);
string cheminSortieDrawRecto = Path.Combine(this.biometrie_sortie_images, $"front_{Guid.NewGuid()}.bmp");
string cheminSortieDrawVerso = Path.Combine(this.biometrie_sortie_images, $"back_{Guid.NewGuid()}.bmp");
rectoCarte.Save(cheminSortieDrawRecto, ImageFormat.Bmp);
versoCarte.Save(cheminSortieDrawVerso, ImageFormat.Bmp);
retour.IsSuccess = true;
retour.chaine = "OK";
try
{
Connection co = new Connection(printer);
if (co.IsOpen())
{
co.SendCommand("Se");
}
}
catch (Exception ex)
{
retour.IsSuccess = false;
retour.chaine = ex.Message;
}
finally
{
try { if (File.Exists(cheminSortieDrawRecto)) File.Delete(cheminSortieDrawRecto); } catch { }
try { if (File.Exists(cheminSortieDrawVerso)) File.Delete(cheminSortieDrawVerso); } catch { }
}
return retour;
}
}
DataBase Acces methods :
public async Task<Personnalisation> GetPersonnalisationByNinLocalAsync(string nin)
{
try
{
var result = await DbTaskQueue.Enqueue<Personnalisation>(async () =>
{
using (var connection = new SQLiteConnection(ConnexionDB.localConnectionString))
{
await connection.OpenAsync();
string query = @"SELECT * FROM impression WHERE LOWER(nin) = LOWER(@nin) LIMIT 1";
using (var cmd = new SQLiteCommand(query, connection))
{
cmd.Parameters.AddWithValue("@nin", nin);
using (var reader = await cmd.ExecuteReaderAsync())
{
bool hasRecord = await reader.ReadAsync();
if (!hasRecord) return null;
return new Personnalisation
{
id = reader["id"] != DBNull.Value ? Convert.ToInt32(reader["id"]) : 0,
numeroEnregistrement = GetSafeString(reader, "numero_enregistrement"),
statutCarte = GetSafeInt(reader, "statut_carte"),
numeroSerie = GetSafeString(reader, "numero_serie"),
nin = GetSafeString(reader, "nin"),
numeroPensionnaire = GetSafeString(reader, "numero_pensionnaire"),
prenom = GetSafeString(reader, "prenom"),
nom = GetSafeString(reader, "nom"),
dateNaissance = GetSafeString(reader, "date_naissance"),
lieuNaissance = GetSafeString(reader, "lieu_naissance"),
sexe = GetSafeString(reader, "sexe"),
nomMere = GetSafeString(reader, "nom_mere"),
prenomMere = GetSafeString(reader, "prenom_mere"),
prenomPere = GetSafeString(reader, "prenom_pere"),
telephone = GetSafeString(reader, "telephone"),
dateDelivrance = GetSafeString(reader, "date_delivrance"),
dateExpiration = GetSafeString(reader, "date_expiration"),
visage = GetSafeString(reader, "visage"),
signature = GetSafeString(reader, "signature")
};
}
}
}
});
return result;
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] GetPersonnalisationByNinLocalAsync: {ex.Message}");
return null;
}
}
public static Task<ResponseData> updatePersonnalisation(Personnalisation p)
{
return DbTaskQueue.Enqueue<ResponseData>(async () =>
{
var response = new ResponseData();
try
{
using (var connection = new SQLiteConnection(ConnexionDB.localConnectionString))
{
await connection.OpenAsync();
using (var transaction = connection.BeginTransaction())
{
try
{
using (var checkCmd = new SQLiteCommand(
"SELECT COUNT(*) FROM impression WHERE id=@id", connection))
{
checkCmd.Parameters.AddWithValue("@id", p.id);
if (Convert.ToInt32(await checkCmd.ExecuteScalarAsync()) == 0)
{
response.status = false;
response.msg = "Record not found.";
return response;
}
}
if (string.IsNullOrWhiteSpace(p.numeroSerie))
{
response.status = false;
response.msg = "Missing serial number.";
return response;
}
using (var updateCmd = new SQLiteCommand(
@"UPDATE impression SET
statut_carte=@statut,
date_modification=@modification,
login=@login,
date_delivrance=@delivrance,
date_expiration=@expiration,
numero_serie=@serie
WHERE id=@id", connection))
{
updateCmd.Parameters.AddWithValue("@statut", p.statutCarte);
updateCmd.Parameters.AddWithValue("@modification", DateTime.UtcNow);
updateCmd.Parameters.AddWithValue("@login", p.login);
updateCmd.Parameters.AddWithValue("@delivrance", p.dateDelivrance);
updateCmd.Parameters.AddWithValue("@expiration", p.dateExpiration);
updateCmd.Parameters.AddWithValue("@serie", p.numeroSerie);
updateCmd.Parameters.AddWithValue("@id", p.id);
await updateCmd.ExecuteNonQueryAsync();
}
using (var cardUpdateCmd = new SQLiteCommand(
@"UPDATE carte_local SET
login=@login,
etat=@etat,
dateModification=@modification
WHERE numeroSerie=@serie", connection))
{
cardUpdateCmd.Parameters.AddWithValue("@login", p.login);
cardUpdateCmd.Parameters.AddWithValue("@etat", p.statutCarte);
cardUpdateCmd.Parameters.AddWithValue("@modification", DateTime.UtcNow);
cardUpdateCmd.Parameters.AddWithValue("@serie", p.numeroSerie);
await cardUpdateCmd.ExecuteNonQueryAsync();
}
transaction.Commit();
response.status = true;
response.msg = "Update successful.";
}
catch
{
transaction.Rollback();
throw;
}
}
}
}
catch (Exception ex)
{
response.status = false;
response.msg = ex.Message;
}
return response;
});
}
public static class DbTaskQueue
{
private static readonly SemaphoreSlim _dbSemaphore = new SemaphoreSlim(4, 4); // 4 accès concurrents
public static async Task<T> Enqueue<T>(Func<Task<T>> taskGenerator)
{
await _dbSemaphore.WaitAsync();
try
{
Console.WriteLine($"[DbTaskQueue] Sémaphore acquis - Restant: {_dbSemaphore.CurrentCount}");
var result = await taskGenerator();
return result;
}
finally
{
_dbSemaphore.Release();
Console.WriteLine($"[DbTaskQueue] Sémaphore libéré - Restant: {_dbSemaphore.CurrentCount}");
}
}
public static async Task Enqueue(Func<Task> action)
{
await _dbSemaphore.WaitAsync();
try
{
await action();
}
finally
{
_dbSemaphore.Release();
}
}
}
Key Issues:
Resource conflicts between PCSC connections across multiple printers
Memory leaks with image processing and card connections
Concurrent access to printer hardware resources
Thread safety issues with shared PCSC context
Proper disposal of card connections and image resources.