Mise à jour: Le contenu de cet article ne s’applique qu’aux versions Acumatica 2021 R1 et antérieures.
Introduction
Dans cet article de blog de série en deux parties, je veux partager avec vous tous comment les opérations asynchrones / synchrones et le travail multithreading dans le cadre Acumatica en utilisant C #. Je vais raconter comment vous pouvez améliorer les performances - ce qui fonctionne et ce qui ne fonctionne pas, ainsi que comment la mise en cache peut vous aider à améliorer les performances hors de la boîte sans nécessiter d’optimisations multi-threading dans votre code. Tout d’abord, il y a les opérations synchrones et asynchrones. Je vais couvrir multithreading dans mon prochain post qui sera publié dans quelques jours.
Opérations synchrones et asynchrones
Très souvent, il est nécessaire de créer des enquêtes personnalisées basées sur des agrégations sophistiquées dans la base de données. Supposons que vous ayez une tâche pour calculer les totaux de toutes les commandes client dans la base de données pour Ordered Qty, Order Total et Tax Total. Vous pouvez l’implémenter d’une manière qui est initialement synchrone, puis asynchrone comme suit :
public PXAction<SOOrder> DifferentTests;
[PXButton]
[PXUIField(DisplayName = "Async test")]
protected virtual IEnumerable differentTests(PXAdapter adapter)
{
PXLongOperation.StartOperation(Base, delegate
{
ExecuteTests();
});
return adapter.Get();
}
private void ExecuteTests()
{
var sw = new Stopwatch();
sw.Start();
var r1 = GetAllCuryOrderTotal1();
var r2 = GetAllCuryTaxTotalTotal1();
var r3 = GetAllOrderQty1();
sw.Stop();
PXTrace.WriteInformation($"Milliseconds passed for sync: {sw.ElapsedMilliseconds}, r1 ={r1}, r2={r2}, r3 = {r3}");
var sw1 = new Stopwatch();
sw1.Start();
var t1 = GetAllCuryOrderTotal2();
var t2 = GetAllCuryTaxTotalTotal2();
var t3 = GetAllOrderQty2();
Task.WhenAll(t1, t2, t3).GetAwaiter().GetResult();
sw1.Stop();
PXTrace.WriteInformation($"Milliseconds passed for async: {sw1.ElapsedMilliseconds}, r1 ={t1.Result}, " +
$"r2={t2.Result}, r3 = {t3.Result}");
}
public decimal GetAllCuryOrderTotal1()
{
decimal sum = 0.0m;
foreach (var soOrder in PXSelect<SOOrder>.Select(Base).ToList())
{
sum += soOrder.GetItem<SOOrder>().CuryOrderTotal ?? 0.0m;
}
return sum;
}
public decimal GetAllCuryTaxTotalTotal1()
{
decimal sum = 0.0m;
foreach (var soOrder in PXSelect<SOOrder>.Select(Base).ToList())
{
sum += soOrder.GetItem<SOOrder>().CuryTaxTotal ?? 0.0m;
}
return sum;
}
public decimal GetAllOrderQty1()
{
decimal sum = 0.0m;
foreach (var soOrder in PXSelect<SOOrder>.Select(Base).ToList())
{
sum += soOrder.GetItem<SOOrder>().OrderQty ?? 0.0m;
}
return sum;
}
public async Task<decimal> GetAllCuryOrderTotal2()
{
decimal sum = 0.0m;
await Task.Run(
() =>
{
foreach (var soOrder in PXSelect<SOOrder>.Select(Base).ToList())
{
sum += soOrder.GetItem<SOOrder>().CuryOrderTotal ?? 0.0m;
}
}
);
return sum;
}
public async Task<decimal> GetAllCuryTaxTotalTotal2()
{
decimal sum = 0.0m;
await Task.Run(
() =>
{
foreach (var soOrder in PXSelect<SOOrder>.Select(Base).ToList())
{
sum += soOrder.GetItem<SOOrder>().CuryTaxTotal ?? 0.0m;
}
}
);
return sum;
}
public async Task<decimal> GetAllOrderQty2()
{
decimal sum = 0.0m;
await Task.Run(
() =>
{
foreach (var soOrder in PXSelect<SOOrder>.Select(Base).ToList())
{
sum += soOrder.GetItem<SOOrder>().OrderQty ?? 0.0m;
}
}
);
return sum;
}
Ensuite, comme vous pouvez le voir dans la capture d’écran de la fenêtre de trace, les détails suivants:
Notez que la version synchrone a pris 27 366 ms pour s’exécuter, et le code asynchrone seulement 423 ms (64 fois plus rapide). Il semblerait que ce serait une bonne idée de réécrire nos demandes personnalisées pour les versions asynchrones de notre code. Cependant, ne vous laissez pas berner car ce serait une mauvaise idée. Dans le fragment de code ci-dessous, je pense que vous comprendrez pourquoi:
private void ExecuteTests()
{
foreach (var soOrder in PXSelect<SOOrder>.Select(Base).ToList())
{
}
var sw = new Stopwatch();
sw.Start();
var r1 = GetAllCuryOrderTotal1();
Voici ce que nous voyons dans la fenêtre de trace :
La version de synchronisation a pris 27.366 ms pour exécuter, et asynchrone seulement 423 (64 fois plus rapide). On dirait qu’il est temps de réécrire les demandes personnalisées pour notre version asynchrone. Mais ne vous précipitez pas pour tirer des conclusions, car c’est une erreur. Je pense que le fragment de code ci-dessous sera explicite:
private void ExecuteTests()
{
foreach (var soOrder in PXSelect<SOOrder>.Select(Base).ToList())
{
}
var sw = new Stopwatch();
sw.Start();
var r1 = GetAllCuryOrderTotal1();
Le reste du code est le même qu’avant, mais prenez note des résultats:
Des résultats surprenants pour être sûr. La sommation synchrone n’a pris que 12 millisecondes, tandis que l’asynchrone a pris 399 millisecondes! La raison derrière une telle amélioration est le mécanisme de mise en cache d’Acumatica. Le Foreach initial qui avait énuméré toutes les commandes client a mis ces commandes client dans le cache d’Acumatica et peut-être que SQL Server a également fait de la mise en cache ici. Le résultat de la sommation synchrone n’a pris que 12 millisecondes au lieu de 27 366 initiales.
Ainsi, l’un des points à renombrer ici pourrait être la ré-énumération des enregistrements peut améliorer les performances. En effet, il place les enregistrements dans le cache et élimine tout aller-retour dans la base de données. Ou, si vous voulez être sûr, il suffit de lire tous les enregistrements de la base de données dans un morceau de mémoire, et d’exécuter les calculs là-bas.
Soit dit en passant, si vous êtes malchanceux, vous pouvez obtenir un message d’erreur:
Sans creuser dans les détails, ce message d’erreur est causé par le fait que les graphiques Acumatica par défaut ne sont pas sécurisés pour les threads. Par conséquent, si vous souhaitez éviter de tels messages d’erreur, vous devrez modifier toutes les méthodes asynchrones que vous pourriez avoir de la manière suivante :
public async Task<decimal> GetAllCuryOrderTotal2()
{
decimal sum = 0.0m;
await Task.Run(
() =>
{
var gr = PXGraph.CreateInstance<SOOrderEntry>();
foreach (var soOrder in PXSelect<SOOrder>.Select(gr).ToList())
{
sum += soOrder.GetItem<SOOrder>().CuryOrderTotal ?? 0.0m;
}
}
);
return sum;
}
L’idée de base ici est que le changement pour chaque thread obtiendra son propre graphique, et donc les threads ne provoqueront pas de collisions dans votre code. J’ai pensé que j’ajouterais une création de graphique au code de synchronisation, et voici les résultats de la trace de pile que j’ai observée:
Vous observez qu’avec une création de graphique séparée (asynchrone vs synchronisation), nous remarquons que l’asynchrone est plus rapide - mais il y a un problème. La version du code de synchronisation n’a pas besoin d’une telle « optimisation ». Je l’ai présenté juste pour vous montrer une comparaison juste des pommes aux pommes.
Voici le code source complet de cette approche :
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using PX.Data;
using PX.Objects.SO;
namespace MultiThreadingAsyncDemo
{
public class SOOrderEntryExt : PXGraphExtension<SOOrderEntry>
{
public PXAction<SOOrder> MultiThreadingTest;
[PXButton]
[PXUIField(DisplayName = "Multi threading test")]
protected virtual IEnumerable multiThreadingTest(PXAdapter adapter)
{
return adapter.Get();
}
public PXAction<SOOrder> DifferentTests;
[PXButton]
[PXUIField(DisplayName = "Async test")]
protected virtual IEnumerable differentTests(PXAdapter adapter)
{
PXLongOperation.StartOperation(Base, delegate
{
ExecuteTests();
});
return adapter.Get();
}
private void ExecuteTests()
{
foreach (var soOrder in PXSelect<SOOrder>.Select(Base).ToList())//this foreach intended for eliminating effect of caching of records in Acumatica
{
}
var sw = new Stopwatch();
sw.Start();
var r1 = GetAllCuryOrderTotal1();
var r2 = GetAllCuryTaxTotalTotal1();
var r3 = GetAllOrderQty1();
sw.Stop();
PXTrace.WriteInformation($"Milliseconds passed for sync: {sw.ElapsedMilliseconds}, r1 ={r1}, r2={r2}, r3 = {r3}");
var sw1 = new Stopwatch();
sw1.Start();
var t1 = GetAllCuryOrderTotal2();
var t2 = GetAllCuryTaxTotalTotal2();
var t3 = GetAllOrderQty2();
Task.WhenAll(t1, t2, t3).GetAwaiter().GetResult();
sw1.Stop();
PXTrace.WriteInformation($"Milliseconds passed for async: {sw1.ElapsedMilliseconds}, r1 ={t1.Result}, " +
$"r2={t2.Result}, r3 = {t3.Result}");
}
public decimal GetAllCuryOrderTotal1()
{
decimal sum = 0.0m;
var gr = PXGraph.CreateInstance<SOOrderEntry>();
foreach (var soOrder in PXSelect<SOOrder>.Select(gr).ToList())
{
sum += soOrder.GetItem<SOOrder>().CuryOrderTotal ?? 0.0m;
}
return sum;
}
public decimal GetAllCuryTaxTotalTotal1()
{
decimal sum = 0.0m;
var gr = PXGraph.CreateInstance<SOOrderEntry>();
foreach (var soOrder in PXSelect<SOOrder>.Select(gr).ToList())
{
sum += soOrder.GetItem<SOOrder>().CuryTaxTotal ?? 0.0m;
}
return sum;
}
public decimal GetAllOrderQty1()
{
decimal sum = 0.0m;
var gr = PXGraph.CreateInstance<SOOrderEntry>();
foreach (var soOrder in PXSelect<SOOrder>.Select(gr).ToList())
{
sum += soOrder.GetItem<SOOrder>().OrderQty ?? 0.0m;
}
return sum;
}
public async Task<decimal> GetAllCuryOrderTotal2()
{
decimal sum = 0.0m;
await Task.Run(
() =>
{
var gr = PXGraph.CreateInstance<SOOrderEntry>();
foreach (var soOrder in PXSelect<SOOrder>.Select(gr).ToList())
{
sum += soOrder.GetItem<SOOrder>().CuryOrderTotal ?? 0.0m;
}
}
);
return sum;
}
public async Task<decimal> GetAllCuryTaxTotalTotal2()
{
decimal sum = 0.0m;
await Task.Run(
() =>
{
var gr = PXGraph.CreateInstance<SOOrderEntry>();
foreach (var soOrder in PXSelect<SOOrder>.Select(gr).ToList())
{
sum += soOrder.GetItem<SOOrder>().CuryTaxTotal ?? 0.0m;
}
}
);
return sum;
}
public async Task<decimal> GetAllOrderQty2()
{
decimal sum = 0.0m;
await Task.Run(
() =>
{
var gr = PXGraph.CreateInstance<SOOrderEntry>();
foreach (var soOrder in PXSelect<SOOrder>.Select(gr).ToList())
{
sum += soOrder.GetItem<SOOrder>().OrderQty ?? 0.0m;
}
}
);
return sum;
}
}
}
Une question logique que l’on pourrait se poser: comment pouvons-nous atteindre un graphique pour les calculs de totaux de synchronisation, et un graphique pour les calculs asynchrones. Après réflexion, je suis venu avec le code suivant:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using PX.Data;
using PX.Objects.SO;
namespace MultiThreadingAsyncDemo
{
public class SOOrderEntryExt : PXGraphExtension<SOOrderEntry>
{
public PXAction<SOOrder> MultiThreadingTest;
[PXButton]
[PXUIField(DisplayName = "Multi threading test")]
protected virtual IEnumerable multiThreadingTest(PXAdapter adapter)
{
return adapter.Get();
}
public PXAction<SOOrder> DifferentTests;
[PXButton]
[PXUIField(DisplayName = "Async test")]
protected virtual IEnumerable differentTests(PXAdapter adapter)
{
PXLongOperation.StartOperation(Base, delegate
{
ExecuteTests();
});
return adapter.Get();
}
private void ExecuteTests()
{
int numberOfIterations = 100;
foreach (var soOrder in PXSelect<SOOrder>.Select(Base).ToList())//this foreach intended for eliminating effect of caching of records in Acumatica
{
}
var sw = new Stopwatch();
sw.Start();
decimal r1, r2, r3;
for(int i = 0; i < numberOfIterations; i++)
{
r1 = GetAllCuryOrderTotal1();
r2 = GetAllCuryTaxTotalTotal1();
r3 = GetAllOrderQty1();
}
sw.Stop();
PXTrace.WriteInformation($"Milliseconds passed for sync: {sw.ElapsedMilliseconds}, r1 ={r1}, r2={r2}, r3 = {r3}");
var sw1 = new Stopwatch();
sw1.Start();
Task<decimal> t1 = null, t2 = null, t3 = null;
var g1 = PXGraph.CreateInstance<SOOrderEntry>();
var g2 = PXGraph.CreateInstance<SOOrderEntry>();
var g3 = PXGraph.CreateInstance<SOOrderEntry>();
for (int i = 0; i < numberOfIterations; i++)
{
t1 = GetAllCuryOrderTotal2(g1);
t2 = GetAllCuryTaxTotalTotal2(g2);
t3 = GetAllOrderQty2(g3);
Task.WhenAll(t1, t2, t3).GetAwaiter().GetResult();
}
sw1.Stop();
PXTrace.WriteInformation($"Milliseconds passed for async: {sw1.ElapsedMilliseconds}, r1 ={t1.Result}, " +
$"r2={t2.Result}, r3 = {t3.Result}");
}
public decimal GetAllCuryOrderTotal1()
{
decimal sum = 0.0m;
foreach (var soOrder in PXSelect<SOOrder>.Select(Base).ToList())
{
sum += soOrder.GetItem<SOOrder>().CuryOrderTotal ?? 0.0m;
}
return sum;
}
public decimal GetAllCuryTaxTotalTotal1()
{
decimal sum = 0.0m;
foreach (var soOrder in PXSelect<SOOrder>.Select(Base).ToList())
{
sum += soOrder.GetItem<SOOrder>().CuryTaxTotal ?? 0.0m;
}
return sum;
}
public decimal GetAllOrderQty1()
{
decimal sum = 0.0m;
foreach (var soOrder in PXSelect<SOOrder>.Select(Base).ToList())
{
sum += soOrder.GetItem<SOOrder>().OrderQty ?? 0.0m;
}
return sum;
}
public async Task<decimal> GetAllCuryOrderTotal2(SOOrderEntry gr)
{
decimal sum = 0.0m;
await Task.Run(
() =>
{
foreach (var soOrder in PXSelect<SOOrder>.Select(gr).ToList())
{
sum += soOrder.GetItem<SOOrder>().CuryOrderTotal ?? 0.0m;
}
}
);
return sum;
}
public async Task<decimal> GetAllCuryTaxTotalTotal2(SOOrderEntry gr)
{
decimal sum = 0.0m;
await Task.Run(
() =>
{
foreach (var soOrder in PXSelect<SOOrder>.Select(gr).ToList())
{
sum += soOrder.GetItem<SOOrder>().CuryTaxTotal ?? 0.0m;
}
}
);
return sum;
}
public async Task<decimal> GetAllOrderQty2(SOOrderEntry gr)
{
decimal sum = 0.0m;
await Task.Run(
() =>
{
foreach (var soOrder in PXSelect<SOOrder>.Select(gr).ToList())
{
sum += soOrder.GetItem<SOOrder>().OrderQty ?? 0.0m;
}
}
);
return sum;
}
}
}
Comme vous pouvez le voir sur le code, j’ai la base revenir à la version de synchronisation, et chaque méthode a sa propre instance de graphique. En outre, afin d’imiter une grande quantité de données (la base de données de démonstration des ventes n’a que 3,348 commandes client), j’ai également introduit cela par cycle.
Et voici les résultats - 100 cycles (égal à 300 000 enregistrements):
1 000 cycles (équivalant à 3 000 000 d’enregistrements) :
Notez que 100 000 cycles (égal à 30 millions d’enregistrements) :
Comme vous pouvez le voir sur la capture d’écran, à 30 millions d’enregistrements, la différence de performance entre sync / async. En représentation numérique, cette différence est 436503/237619 ≈ 1,837 Afin de continuer, je veux ajouter quelques ajouts supplémentaires Si conditions pour rendre les calculs logiques plus complexes, et voir si cela aura un effet notable. Ci-dessous donne un échantillon des modifications que j’ai apportées:
public bool IsMultipleOf2(string str)
{
try
{
char last = str[str.Length - 1];
int number = int.Parse(last.ToString());
return number % 2 == 0;
}
catch
{
return true;
}
}
public decimal GetAllCuryOrderTotal1()
{
decimal sum = 0.0m;
foreach (var soOrder in PXSelect<SOOrder>.Select(Base).ToList())
{
if (IsMultipleOf2(soOrder.GetItem<SOOrder>().OrderNbr))
{
sum += soOrder.GetItem<SOOrder>().CuryOrderTotal ?? 0.0m;
}
else
{
var number = soOrder.GetItem<SOOrder>().CuryOrderTotal ?? 0.0m;
sum += number * number;
}
}
return sum;
}
Comme vous pouvez le voir sur le code, j’ai ajouté des modifications relativement petites, mais jetez un oeil quel effet il a sur la différence de performance:
913838 / 430728 ≈ 2.12
Voici à nouveau le code source complet:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using PX.Data;
using PX.Objects.SO;
namespace MultiThreadingAsyncDemo
{
public class SOOrderEntryExt : PXGraphExtension<SOOrderEntry>
{
public PXAction<SOOrder> MultiThreadingTest;
[PXButton]
[PXUIField(DisplayName = "Multi threading test")]
protected virtual IEnumerable multiThreadingTest(PXAdapter adapter)
{
return adapter.Get();
}
public PXAction<SOOrder> DifferentTests;
[PXButton]
[PXUIField(DisplayName = "Async test")]
protected virtual IEnumerable differentTests(PXAdapter adapter)
{
PXLongOperation.StartOperation(Base, delegate
{
ExecuteTests();
});
return adapter.Get();
}
private void ExecuteTests()
{
int numberOfIterations = 100000;
foreach (var soOrder in PXSelect<SOOrder>.Select(Base).ToList())//this foreach intended for eliminating effect of caching of records in Acumatica
{
}
var sw = new Stopwatch();
sw.Start();
decimal r1 = 0, r2 = 0, r3 = 0;
for(int i = 0; i < numberOfIterations; i++)
{
r1 = GetAllCuryOrderTotal1();
r2 = GetAllCuryTaxTotalTotal1();
r3 = GetAllOrderQty1();
}
sw.Stop();
PXTrace.WriteInformation($"Milliseconds passed for sync: {sw.ElapsedMilliseconds}, r1 ={r1}, r2={r2}, r3 = {r3}");
var sw1 = new Stopwatch();
sw1.Start();
Task<decimal> t1 = null, t2 = null, t3 = null;
var g1 = PXGraph.CreateInstance<SOOrderEntry>();
var g2 = PXGraph.CreateInstance<SOOrderEntry>();
var g3 = PXGraph.CreateInstance<SOOrderEntry>();
for (int i = 0; i < numberOfIterations; i++)
{
t1 = GetAllCuryOrderTotal2(g1);
t2 = GetAllCuryTaxTotalTotal2(g2);
t3 = GetAllOrderQty2(g3);
Task.WhenAll(t1, t2, t3).GetAwaiter().GetResult();
}
sw1.Stop();
PXTrace.WriteInformation($"Milliseconds passed for async: {sw1.ElapsedMilliseconds}, r1 ={t1.Result}, " +
$"r2={t2.Result}, r3 = {t3.Result}");
}
public bool IsMultipleOf2(string str)
{
try
{
char last = str[str.Length - 1];
int number = int.Parse(last.ToString());
return number % 2 == 0;
}
catch
{
return true;
}
}
public decimal GetAllCuryOrderTotal1()
{
decimal sum = 0.0m;
foreach (var soOrder in PXSelect<SOOrder>.Select(Base).ToList())
{
if (IsMultipleOf2(soOrder.GetItem<SOOrder>().OrderNbr))
{
sum += soOrder.GetItem<SOOrder>().CuryOrderTotal ?? 0.0m;
}
else
{
var number = soOrder.GetItem<SOOrder>().CuryOrderTotal ?? 0.0m;
sum += number * number;
}
}
return sum;
}
public decimal GetAllCuryTaxTotalTotal1()
{
decimal sum = 0.0m;
foreach (var soOrder in PXSelect<SOOrder>.Select(Base).ToList())
{
if (IsMultipleOf2(soOrder.GetItem<SOOrder>().OrderNbr))
{
sum += soOrder.GetItem<SOOrder>().CuryTaxTotal ?? 0.0m;
}
else
{
var number = soOrder.GetItem<SOOrder>().CuryTaxTotal ?? 0.0m;
sum += number * number;
}
}
return sum;
}
public decimal GetAllOrderQty1()
{
decimal sum = 0.0m;
foreach (var soOrder in PXSelect<SOOrder>.Select(Base).ToList())
{
if (IsMultipleOf2(soOrder.GetItem<SOOrder>().OrderNbr))
{
sum += soOrder.GetItem<SOOrder>().OrderQty ?? 0.0m;
}
else
{
var number = soOrder.GetItem<SOOrder>().OrderQty ?? 0.0m;
sum += number * number;
}
}
return sum;
}
public async Task<decimal> GetAllCuryOrderTotal2(SOOrderEntry gr)
{
decimal sum = 0.0m;
await Task.Run(
() =>
{
foreach (var soOrder in PXSelect<SOOrder>.Select(gr).ToList())
{
if (IsMultipleOf2(soOrder.GetItem<SOOrder>().OrderNbr))
{
sum += soOrder.GetItem<SOOrder>().CuryOrderTotal ?? 0.0m;
}
else
{
var number = soOrder.GetItem<SOOrder>().CuryOrderTotal ?? 0.0m;
sum += number * number;
}
}
}
);
return sum;
}
public async Task<decimal> GetAllCuryTaxTotalTotal2(SOOrderEntry gr)
{
decimal sum = 0.0m;
await Task.Run(
() =>
{
foreach (var soOrder in PXSelect<SOOrder>.Select(gr).ToList())
{
if (IsMultipleOf2(soOrder.GetItem<SOOrder>().OrderNbr))
{
sum += soOrder.GetItem<SOOrder>().CuryTaxTotal ?? 0.0m;
}
else
{
var number = soOrder.GetItem<SOOrder>().CuryTaxTotal ?? 0.0m;
sum += number * number;
}
}
}
);
return sum;
}
public async Task<decimal> GetAllOrderQty2(SOOrderEntry gr)
{
decimal sum = 0.0m;
await Task.Run(
() =>
{
foreach (var soOrder in PXSelect<SOOrder>.Select(gr).ToList())
{
if (IsMultipleOf2(soOrder.GetItem<SOOrder>().OrderNbr))
{
sum += soOrder.GetItem<SOOrder>().OrderQty ?? 0.0m;
}
else
{
var number = soOrder.GetItem<SOOrder>().OrderQty ?? 0.0m;
sum += number * number;
}
}
}
);
return sum;
}
}
}
Avec juste un peu de logique supplémentaire, vous pouvez voir que cela vous donne la différence de performances dans le temps d’exécution de la version sync / async avec l’amélioration marquée de la version asynchrone du code.
Résumé
Dans cet article de blog, j’ai décrit l’une des deux façons d’accélérer les performances à l’aide de tâches asynchrones. Dans la deuxième partie, j’illustrerai certaines approches multitâche / multi-threading. Ces deux approches peuvent améliorer considérablement les performances, mais pas dans 100% des cas. Les augmentations de performances réelles ne seront gagnées que si vous avez des quantités significatives de données à importer, manipuler ou masser. Ce dont nous parlons ici, ce sont des données dans les millions d’enregistrements.