Actualización de datos paralelos en la API web ASP.NET

Quiero contarles cómo organizamos una actualización de datos de fondo durante una solicitud a un servicio REST.

La tarea es la siguiente: el sistema almacena los datos del usuario. El servicio funciona de forma aislada y no tiene acceso directo a las bases de datos con estos datos. Para que el servicio funcione, es necesario tener los nombres y apellidos de los usuarios en su base de datos interna. Se pueden obtener de la identidad del usuario actual en el momento de la solicitud. Se requiere para agregar o actualizar nombres durante cada solicitud. Es aconsejable implementar esto en un hilo separado para que este trabajo no afecte el tiempo de ejecución de la solicitud principal.

Refinamiento de tareas


En la base de datos del servicio, almacenamos los nombres y apellidos de los usuarios. Los clientes los necesitan para obtener información sobre quién creó o modificó el recurso.

Estos datos no son sistemáticamente significativos: si de repente en la base de datos no hay registros necesarios, no pasará nada malo. Por lo tanto, no queremos registrar nuestro trabajo en segundo plano en ASP utilizando QueueBackgroundWorkItem, para complicar la sobrecarga del dominio de la aplicación.
Es aconsejable resolver el problema lo más simple posible.

Para aquellos que quieran aprender más sobre tareas en segundo plano en ASP.NET, les aconsejo que lean un buen artículo al respecto.

Solución


Tenemos una clase DbRefresher que agrega o cambia datos de usuario en el método RefreshAsync.

Nuestros controladores usan el atributo Autorizar. Agregue nuestro descendiente a esta clase y anule el método OnAuthorization:

public override void OnAuthorization(HttpActionContext actionContext) { base.OnAuthorization(actionContext); if (IsAuthorized(actionContext)) DbRefresher.RefreshAsync(actionContext.RequestContext.Principal) .ContinueWith(t => { LogFactory.For<AuthorizeAndRefreshUserAttribute>() .ErrorException("Error occured", t.Exception); }, TaskContinuationOptions.OnlyOnFaulted); } 

DbRefresher.RefreshAsync es un método asincrónico que devuelve un objeto Task que continúa ejecutándose en otro hilo. El método OnAuthorize se cierra inmediatamente sin esperar a que se complete la tarea. En el caso de un bloqueo, se agregará un mensaje de error al registro.

Eso es todo: todo lo que queda es reemplazar el atributo Autorizar en los controladores con el nombre de nuestro nuevo atributo. El nuevo atributo devuelve el control inmediatamente después de verificar los derechos del usuario actual, después de lo cual el controlador comienza su trabajo. La base de datos se actualizará en paralelo con la preparación de la respuesta por parte del controlador.

Prueba


Una prueba que ejecuta varias solicitudes de servicio simultáneas ayudará a identificar posibles problemas:

 [TestMethod] public void ConcurrentTest() { const int threadCount = 10; var tasks = new Task[threadCount]; for (int i = 0; i < threadCount; i++) { // DoOperations contains several CRUD operations on resources tasks[i] = Task.Factory.StartNew(DoOperations); } Task.WaitAll(tasks); } 

Los problemas


Si para el funcionamiento de algunos métodos de acción del controlador se requiere que la base de datos obviamente contenga los datos del usuario actual, este enfoque no es adecuado. Tendrá que utilizar una llamada explícita a DbRefresher.RefreshAsync en el cuerpo del método.

Puede haber problemas al agregar un nuevo usuario a la base de datos con varias solicitudes simultáneas. Si se intenta agregar un usuario a una tabla con una clave existente, debe detectar la excepción de violación de clave principal y dejar de funcionar. Luego, todo el trabajo para actualizar los datos del usuario será realizado por un solo hilo.

Conclusión


Este enfoque ha funcionado con éxito en uno de nuestros servicios en Konfermit durante más de un año.
Me parece simple y elegante. Sería interesante saber la opinión de la comunidad.

Source: https://habr.com/ru/post/439628/


All Articles