Multithreading In Acumatica
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.