1

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.

6
  • Get 3 more machines or create 4 virtual ones. Commented Nov 15 at 6:52
  • 1
    Reading your async code shows nearly any mistake you can do with async code. Commented Nov 15 at 7:49
  • Can you please help me? I don't understand what I did wrong, and I would like some explanations, please. What should I change? Why, when I launch three simultaneous print jobs—for example, 10 cards on each printer—do I sometimes get only 5 cards printed on one printer, sometimes more, but rarely all 10 on each printer are printed ? Commented Nov 15 at 10:10
  • Unfortunately, your question lacks focus. The expression experiencing resource conflicts, deadlocks, and memory issues is not definitive. To get help, you have to focus only on one conflict or error, provide a comprehensive issue report with a highly simplified code sample. Please see: How to create a Minimal, Reproducible Example. Commented Nov 16 at 1:45
  • I'm not getting any errors, I'm just not getting any response from the printers. If I print 50 cards on a single printer, they all printed, but as soon as I print on two printers simultaneously, the other one prints somes card, and after a few cards, they both stop without any error messages or anything. Please i really want help ! just tell me, in my code whats wrong et how to fix it ? Please @SirRufo Commented Nov 16 at 18:40

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.