Loading ...

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:

  1. Create some graph instance.
  2. Fill created graph instance data views with needed data
  3. 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<SubWhere<Sub.subCDEqual<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.