'How to add Lock and await combined together. ( Snippet not producing right output )
I am facing a strange behavior or async-await while combined with database calls. All I want to synchronize is the addition of a few items within the databases. Because I want to employ the rule of overlapping two items ( i.e. they must not be added and exceptions is being thrown )
But somehow, locks are not working and in addition i can see there are more than two threads are coming in the method while releasing the locks their ids completely get different in comparison with the ids of the threads those who entered in the critical section.
No Idea why? Am I locking wrong way? Why many threads out of just 2 tasks. ( how to add synchronization if this locking is bad or wrong )
private readonly SemaphoreSlim accessPoint = new SemaphoreSlim(1,1);
public async Task<ScheduleResponseDto> AddItemToSchedule(int scheduleId, ScheduleInputItemDto scheduleItem)
{
await accessPoint.WaitAsync();
try
{
// I can see here more than 2 threads and they entered without respecting the lock statement ...
var Id = Thread.CurrentThread.ManagedThreadId;
var scheduleWithId = await _scheduleRepository.GetScheduleById(scheduleId);
scheduleWithId.AddItem(start: scheduleItem.Start,end: scheduleItem.End,cementType: scheduleItem.CementType,now: DateTime.UtcNow);
await _scheduleRepository.Update(scheduleWithId);
return scheduleWithId.MapToScheduleDto();
}
finally
{
var Id2 = Thread.CurrentThread.ManagedThreadId;
accessPoint.Release();
}
return null;
}
public async Task<Domain.Schedule.Models.Schedule> GetScheduleById(int scheduleId)
{
return await FindByInclude(it => it.ScheduleId == scheduleId, it => it.ScheduleItems).SingleAsync();
}
public void AddItem(DateTime start, DateTime end, string cementType, DateTime now)
{
var item = new ScheduleItem(start, end, cementType, now);
item.ValidateDoesNotOverlapWithItems(ScheduleItems.ToList()); // This is where I need to stop overlapping ( This must throws exception if overlapping occurs )
ScheduleItems.Add(item);
}
public Task<int> Update(TEntity entity)
{
_dbSet.Attach(entity);
_context.SetModified(entity);
return DelaySave();
}
// This delay is put in place to ensure that the concurrency problem is reproducible.
private async Task<int> DelaySave()
{
await Task.Delay(1000);
return await _context.SaveChangesAsync();
}
public static ScheduleItemResponseDto MapToScheduleItemDto(this ScheduleItem scheduleItem)
{
return new ScheduleItemResponseDto
{
End = scheduleItem.End,
Start = scheduleItem.Start,
CementType = scheduleItem.CementType,
ScheduleId = scheduleItem.ScheduleId,
UpdatedOn = scheduleItem.UpdatedOn,
NumberOfTimesUpdated = scheduleItem.NumberOfTimesUpdated,
ScheduleItemId = scheduleItem.ScheduleItemId
};
}
public static ScheduleResponseDto MapToScheduleDto(this Schedule schedule)
{
return new ScheduleResponseDto
{
PlantCode = schedule.PlantCode,
ScheduleId = schedule.ScheduleId,
UpdatedOn = schedule.UpdatedOn,
ScheduleItems = schedule.ScheduleItems.Select(MapToScheduleItemDto).ToList()
};
}
and now Following is the HttpClient which calls the above routine.
public async Task<HttpResponseMessage> Post<T>(T content, bool throwOnError = true) where T : class
{
var Id = Thread.CurrentThread.ManagedThreadId;
var response = await _httpClient.PostAsync(BuildUrl(), ToStringContent(content));
if(throwOnError) ThrowIfError(response);
return response;
}
following is the controller method which get called by post calls.
[HttpPost("items")]
[ProducesResponseType(typeof(ScheduleResponseDto), StatusCodes.Status200OK)]
[Produces(MediaTypeNames.Application.Json)]
public async Task<ActionResult<ScheduleItemResponseDto>> PostScheduleItem(int scheduleId, ScheduleInputItemDto scheduleInputItem)
{
var scheduleItem = await _scheduleService.AddItemToSchedule(scheduleId, scheduleInputItem);
return Ok(scheduleItem);
}
and lastly following is the call which calls the http client and further from there point ownwards calls the SchedularService methods for inserting items into schedule.
Following line adding the same item multiple times to databases. There is a mathod ValidateDoesNotOverlapWithItems, which have the valid check. It extracts the items from databases and inspect if incoming has already added. But if both threads ( tasks ) don't have locks in place , it would end up adding the same item multiple times because threads dont follow certain order. var itemAddResponses = await Task.WhenAll(addItemRequest.Post(itemToAdd, false), addItemRequest.Post(itemToAdd, false));
Here above you can see only two tasks are created not more than that. But I can see 4 threads produced inside critical section and among those 4 only two appeared in release area. Moreover they are not respecting locking and coming in critical section without waiting the other has released or not.
Why this is happening.?
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
