V
V
Vladimir2019-03-05 02:26:27
ASP.NET
Vladimir, 2019-03-05 02:26:27

How to isolate code execution by multiple threads?

Simplified example:
asp.net mvc application
Let's say the user makes an order
We have the OrderService class - for placing an order and UserService for operations on the cash balance
. We place an order by making an ajax post request from the client to the controller (HomeController) from which the OrderService is already twitching

public class OrderService
    {
        private readonly UserService _userService;
        private readonly DbContext _dbContext;

        public OrderService(DbContext dbContext, UserService userService)
        {
            _userService = userService;
            _dbContext = dbContext;
        }

        public int AddOrder(decimal totalSum)
        {
            var order = new OrderEntity
            {
                TotalSum = totalSum
            };

            var balance = _userService.GetBalance();
            
            // проверяем баланс
            if (balance < order.TotalSum)
            {
                throw new Exception("Not enough money");
            }

            // списываем деньги
            _userService.OperateBalance(-order.TotalSum);

            _dbContext.Set<OrderEntity>().Add(order);

            _dbContext.SaveChanges();

            return order.Id;
        }
    }

public class UserService
    {
        private readonly DbContext _dbContext;
        public UserService(DbContext dbContext)
        {
            _dbContext = dbContext;
        }

        public decimal GetBalance()
        {
            return _dbContext.Set<UserEntity>().FirstOrDefault(x => x.Id == userId)?.Balance;
        }

        public void OperateBalance(decimal amount)
        {
            var user = _dbContext.Set<UserEntity>().FirstOrDefault(x => x.Id == userId);
            if (user != null)
            {
                user.Balance += amount;
                _dbContext.SaveChanges();
            }
        }
    }

public class HomeController : Controller
    {
        private readonly IOrderService _service;

        public HomeController(IOrderService service)
        {
            _service = service;
        }

        [HttpPost]
        public JsonResult PostOrder(decimal totalSum)
        {
            var result = _service.AddOrder(totalSum);
            
            return Json(result);
        }
    }

$.post('/Home/PostOrder', { totalSum: 150 })
                        .success(function(res) {
                             console.info('res1', res);
                        });

Problem: if you make two simultaneous requests, then two threads will enter the AddOrder method at the same time and both will give a correct check on the user's balance.
I see only one solution - statick lock on the entire method
. Is this generally a normal practice? Are there any other options?
ps DbContext is managed by Unity via PerRequestLifetimeManager
UserService and OrderService - default (PerResolveLifetimeManager)

Answer the question

In order to leave comments, you need to log in

2 answer(s)
E
eRKa, 2019-03-05
@brainshock

First, if the OrderService is not resolved as a singleton, then a separate service instance will be created for each request, i.e. there will not be two threads per method.
Second, if it resolves as a singleton, then you're in trouble. inside dbContext there will be one for all requests, and it is better to create it on a new one for each request.
Third, in order to avoid two simultaneous requests from one client, you need to disable the button and show the preloader until a response is received.
And fourthly, if you want to get the balance before another request adds something there, then in AddOrder use transactions with a sufficient level of change lock.

A
Alexander, 2019-03-05
@alexr64

Is this generally normal practice? Are there any other options?

Blocking request handler threads is a bad, harmful practice. Threads are not infinite resources, both in themselves and the computational resources required by threads. You just have a chance that all requests will not have enough RAM.
1 thread processing orders one by one. This may be 1 thread per user, there may not even be threads as such: a flag that prohibits the creation / processing of orders if the user has an unprocessed order. But most often, this is a separate (micro) service that actually receives orders from the web server, puts them in a queue, and already works with this queue.
The main task is to organize the queue.

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question