Hello everybody,
today I want to say a few words about my practice of usage Multithreading in Acumatica. I have a dedicated server which has 40 cores. And I have a need to read plenty of records from some other system. General schema is like this:
- Create some graph instance.
- Fill created graph instance data views with needed data
- At created graph instance call method graphInstance.Actions.PressSave();
And that schema is perfectly workable, but for my disappoitment my 40 cores were not busy at all with doing something useful. So I had to figure out how to use them more fully.
Here are some fragments of code that I've used:
int numberOfLogicalCores = Environment.ProcessorCount;
List<Thread> threads = new List<Thread>(numberOfLogicalCores);
int sizeOfOneChunk = (customers.Count / numberOfLogicalCores) + 1;
for (int i = 0; i < numberOfLogicalCores; i++)
{
int a = i;
var thr = new Thread(
() =>
{
var portionsCustomers = customers.Skip(a * sizeOfOneChunk).Take(sizeOfOneChunk).ToList();
InsertCustomersFromList(portionsCustomers);
}
);
thr.Name = $"thr{i}";
threads.Add(thr);
}
foreach (var thread in threads)
{
thread.Start();
}
foreach (var thread in threads)
{
thread.Join();
}
As you can see from presented code it doesn't do anything fancy. Get number of logical processors, for each logical processor creates Thread, each thread creates it's own graph instance ( in function InsertCustomersFromList), called method Actions.PressSave().
Also in graph I have the following syncrhonization object:
private Object thisLock = new Object();
Take a look at how lock is used:
custMaint.Clear(); // This graph was created before
.
.
.
//some filling fields logic
custMaint.DefAddress.SetValueExt<Address.addressLine1>(custMaint.DefAddress.Current, customer.AddressLine1);
custMaint.DefAddress.SetValueExt<Address.addressLine2>(custMaint.DefAddress.Current, customer.AddressLine2);
custMaint.DefAddress.SetValueExt<Address.city>(custMaint.DefAddress.Current, customer.City);
custMaint.DefAddress.SetValueExt<Address.countryID>(custMaint.DefAddress.Current, customer.Country);
custMaint.DefAddress.SetValueExt<Address.state>(custMaint.DefAddress.Current, customer.StateCode);
custMaint.DefAddress.SetValueExt<Address.postalCode>(custMaint.DefAddress.Current, customer.PostalCode);
var subAccount = PXSelect<Sub, Where<Sub.subCD, Equal<Required<Sub.subCD>>>>
.Select(custMaint, customer.SalesSubaccount.Replace("-", "")).FirstOrDefault();
if (subAccount != null)
{
var subAcTyped = subAccount.GetItem<Sub>();
custMaint.DefLocation.SetValueExt<LocationExtAddress.cSalesSubID>(custMaint.DefLocation.Current,
subAcTyped.SubID);
}
lock (thisLock)
{
custMaint.Actions.PressSave();
}
And that's it with multithreading. In my tests I've got improvement in 3 times! In sigle threaded mode 110 recoreds went into Acumatica during 3 minutes, but with multithreading feature records went there in 1 minute.