SemaphoreSlim 不对并发请求进行排队,仍然允许重复预订

SemaphoreSlim doesn't queue concurrent requests and still allows double bookings

我在 .NET Core (2.2) 中使用 SemaphoreSlim class 时遇到问题,希望有人能提供帮助。

我有一个 API 方法 (AddBooking),可以将预订添加到数据库中,此方法包括 3 个等待的存储库 AddAsync() 方法,可将数据添加到 3 个单独的表中。

我看到的所有 guides/docs class 用法略有不同,但 none 似乎实际上是在排队并发请求,导致重复预订。

单步执行代码时,信号量的 CurrentValue 在请求进行时从 1 变为 0,然后在调用 Release() 后变回 1。

private readonly SemaphoreSlim _asyncLock;
_asyncLock = new SemaphoreSlim(1); // in the constructor

public async Task<BookingResponse> AddBooking(NewBooking newBooking)
        {
            await _asyncLock.WaitAsync().ConfigureAwait(false);

            try
            {
                if (!IsWithinBookingTimeframe(newBooking))
                    return new BookingResponse { Result = false, Message = "Bookings must be between 6am and 8pm" };

                AmendIfDayLightSavings(newBooking);

                var bookingDetailsResponse = await CreateBookingDetails(newBooking);
                if (!bookingDetailsResponse.Result)
                    return bookingDetailsResponse;

                var parkingSpaceBookingResponse = await CreateParkingSpaceBooking(newBooking, bookingDetailsResponse.BookingDetails);
                if (!parkingSpaceBookingResponse.Result)
                    return parkingSpaceBookingResponse;

                await _unitOfWork.Save();
                return new BookingResponse { Task = "AddBooking", Result = true, Message = "Booking added successfully", BookingDetails = bookingDetailsResponse.BookingDetails, ParkingSpaces = parkingSpaceBookingResponse.ParkingSpaces };
            }
            catch (Exception exception)
            {
                return new BookingResponse { Task = "AddBooking", Result = false, Message = $"Error adding booking to database: {exception}" };
            }
            finally
            {
                _asyncLock.Release();
            }
        }

我对这个class的理解是,如果CurrentCount为0,后续请求会排队,直到信号量被释放,CurrentCount递增到1(从而允许下一个任务通过),但是同时发出请求仍然会导致重复预订。

一切都取决于在何处以及由谁实例化您的异步时钟。您可能有多个 class 实例,每个实例都有自己的锁,这使得它们看不到其他请求。

基本上你会收到一个请求 -> 实例化你的 class -> 获取锁 -> 生成预订

您必须确保信号量实例在您的 class 实例之间共享,这将 运行 此代码。

作为提示,将 log/breakpoint 插入到 class 构造函数中。您可能会看到它被多次实例化(并创建多个信号量)

你应该这样称呼它:_asyncLock = new SemaphoreSlim(1, 1);

因为第一个参数是初始计数,第二个是最大计数。 现在你只提供了一个初始计数,在第一次调用 Release() 之后你可以无限制地调用它。

或者换句话说,第二个参数禁止超过n个并发调用。 第一个参数表示在第一次调用 Release() 之前可以进行多少次调用。