Inicio Blog Mejora del rendimiento - Primera parte: Operaciones asíncronas/síncronas en Acumatica

Mejora del rendimiento - Primera parte: Operaciones asíncronas/síncronas en Acumatica

Yuriy Zaletskyy | 30 de junio de 2023

Actualización: El contenido de este artículo sólo es aplicable a las versiones 2021 R1 y anteriores de Acumatica.

Mejora del rendimiento - Primera parte: Operaciones asíncronas/síncronas en Acumatica

Introducción

En esta serie de dos entradas de blog, quiero compartir con todos ustedes cómo funcionan lasoperaciones asíncronas/síncronas y los subprocesos múltiples dentro del marco de Acumatica utilizando C#. Explicaré cómo puede mejorar el rendimiento: qué funciona y qué no, y cómo el almacenamiento en caché puede ayudarle a mejorar el rendimiento de forma inmediata sin necesidad de optimizar el multiproceso en el código. En primer lugar, las operaciones síncronas y asíncronas. Cubriré el multithreading en mi próximo post que será publicado en un par de días.

Operaciones síncronas y asíncronas

Muy a menudo existe la necesidad de crear consultas personalizadas que se basan en algunas agregaciones sofisticadas en la base de datos. Supongamos que tiene una tarea para calcular los totales de todos los pedidos de venta en la base de datos para Cantidad Pedida, Total del Pedido y Total de Impuestos. Puede implementarlo de forma que inicialmente sea síncrono y luego asíncrono de la siguiente manera:



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;
}

 

A continuación, como se puede ver en la captura de pantalla de la ventana de rastreo los siguientes detalles:

Ventana de rastreo.

Nótese que la versión síncrona tardó 27.366 ms en ejecutarse, y el código asíncrono sólo 423 ms (64 veces más rápido). Parecería que sería una buena idea reescribir nuestras consultas personalizadas para las versiones asíncronas de nuestro código. Sin embargo, no nos engañemos, ya que sería una mala idea. En el fragmento de código a continuación, creo que entenderás por qué:

 


private void ExecuteTests()
		{
		           	                                 foreach (var soOrder in PXSelect<SOOrder>.Select(Base).ToList())
			{
			}
 
			var sw = new Stopwatch();
			sw.Start();
 
			var r1 = GetAllCuryOrderTotal1();

 

Esto es lo que vemos en la ventana de seguimiento:

 

 

La versión sync tardó 27.366 ms en ejecutarse, y la async sólo 423 (64 veces más rápido). Parece que ha llegado el momento de reescribir las consultas personalizadas para nuestra versión async. Pero no te apresures a sacar conclusiones, ya que esto es un error. Creo que el fragmento de código siguiente se explicará por sí mismo:

 


private void ExecuteTests()
		{
		           	                                 foreach (var soOrder in PXSelect<SOOrder>.Select(Base).ToList())
			{
			}
 
			var sw = new Stopwatch();
			sw.Start();
 
			var r1 = GetAllCuryOrderTotal1();

 

El resto del código es el mismo que antes, pero toma nota de los resultados:

 

Los resultados son sorprendentes. La suma sincrónica tardó sólo 12 milisegundos, ¡mientras que la asincrónica tardó 399 milisegundos! La razón de esta mejora es el mecanismo de almacenamiento en caché de Acumatica. El Foreach inicial que había enumerado todos los pedidos de ventas puso esos pedidos de ventas en la caché de Acumatica y quizás el servidor SQL también hizo algo de caché aquí. El resultado de la suma sincrónica tardó sólo 12 milisegundos en lugar de los 27.366 iniciales.

Por lo tanto, uno de los puntos a tener en cuenta aquí podría ser que la re-enumeración de registros puede mejorar el rendimiento. Esto se debe a que coloca los registros en la caché y elimina cualquier ida y vuelta a la base de datos. O, si quieres estar seguro, simplemente lee todos los registros de la base de datos en algún lugar de la memoria, y ejecuta los cálculos allí.

Por cierto, si tienes mala suerte, puedes recibir un mensaje de error:

 

Sin entrar en detalles, este mensaje de error es causado por el hecho de que los gráficos de Acumatica por defecto no son thread safe. Por lo tanto, si quieres evitar estos mensajes de error, tendrás que modificar los métodos async que puedas tener de la siguiente manera:

 


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;
}

 

La idea básica aquí es que el cambio para cada hilo obtendrá su propio gráfico, y así los hilos no causarán colisiones en tu código. Se me ocurrió añadir una creación de gráfico al código de sincronización, y aquí están los resultados de la pila de seguimiento que he observado:

Observas que con una creación de gráfico separada (async vs sync) notamos que async es más rápido - pero hay un problema. La versión de código sync no necesita tal "optimización". Lo introduje sólo para mostrar una comparación justa de manzanas con manzanas.

Aquí está el código fuente completo de este enfoque:


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;
		}
	}
}

Una pregunta lógica que uno podría hacerse: ¿cómo logramos un gráfico para los cálculos de totales sincronizados y otro para los cálculos asincrónicos? Después de pensarlo un poco, se me ocurrió el siguiente código:


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;
		}
	}
}


Como se puede ver en el código, tengo la base de volver a la versión de sincronización, y cada método tiene su propia instancia de gráfico. Además, con el fin de imitar una gran cantidad de datos (base de datos de demostración de ventas tiene sólo 3.348 órdenes de venta ), también introduje este por ciclo.

Y aquí están los resultados: 100 ciclos (equivalentes a 300.000 registros):

1.000 ciclos (igual a 3.000.000 de registros):

 

Ten en cuenta que 100.000 ciclos (equivalen a 30 millones de registros):

 

Como se puede ver en la captura de pantalla, a 30 millones de registros, la diferencia de rendimiento entre sync/async. En representación numérica esta diferencia es de 436503/237619 ≈ 1,837 Para continuar, quiero añadir algunos adicionales Si condiciones para hacer más complejos los cálculos lógicos, y ver si tiene algún efecto notable. A continuación se da una muestra de los cambios que hice:


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;
}

 

Como puedes ver en el código, he añadido cambios relativamente pequeños, pero echa un vistazo al efecto que tiene en la diferencia de rendimiento:

 

 

913838 / 430728 ≈ 2.12

Aquí está de nuevo el código fuente completo:

 


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;
		}
	}
}

 

Con sólo un poco de lógica adicional, se puede ver que esto le da la diferencia de rendimiento en tiempo de ejecución de la versión sync/async con la marcada mejora de la versión async del código.

Resumen

En esta entrada del blog, he descrito una de las dos formas de acelerar el rendimiento utilizando tareas asíncronas. En la segunda parte, ilustraré algunos enfoques de multitarea/multihilo. Ambos enfoques pueden mejorar el rendimiento de forma significativa, pero no en el 100% de los casos. Los aumentos reales de rendimiento sólo se obtendrán si tiene que importar, manipular o masajear cantidades significativamente grandes de datos. Estamos hablando de millones de registros.

Autor del blog

Yuriy empezó a programar en 2003 utilizando C++ y FoxPro, para pasar después a .Net en 2006. A partir de 2013, ha estado desarrollando activamente aplicaciones utilizando Acumatica xRP Framework, desarrollando soluciones para muchos clientes a lo largo de los años. Tiene un blog personal, acertadamente llamado Blog de Yuriy Zaletskyy, donde ha estado documentando los problemas de programación que se ha encontrado en los últimos seis años, compartiendo sus observaciones y soluciones libremente con otros desarrolladores de Acumatica.

Reciba las actualizaciones del blog en su bandeja de entrada.