public class MyDbContext : DbContext
{
....
static readonly object lockObject = new object();
public override int SaveChanges()
{
lock (lockObject)
return base.SaveChanges();
}
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
lock (lockObject)
return base.SaveChangesAsync(cancellationToken);
}
}
I want to use DbContext in multiple threads, but the underlying database has a problem if two threads are writing at the same time. So I am using lockObject to prevent two SaveChanges() at the same time.
That works fine in SaveChanges(), but I don't know how to do the same in SaveChangesAsync(). I think the solution above is wrong. How can I apply the lock to SaveChangesAsync?
Thank you!
Using a plain lock won't work because you can't await inside of a lock. I decided to try a SemaphoreSlim as that has a async method.
Async locks Trying to release such a lock on any thread other than the one that acquired it will result in an exception. In fact, the C# compiler will even generate a build error if you try to await inside a lock statement to save you from trouble.
Technically, yes, but it won't work as you expect. There are two reasons why thread-affine locks don't play well with async . One is that (in the general case), an async method may not resume on the same thread, so it would try to release a lock it doesn't own while the other thread holds the lock forever.
AsyncLock is an open source library/wrapper around SemaphoreSlim that adds reëntrance and recursion without taking await async / await functionality. Using AsyncLock couldn't be simpler: simply swap any lock (lockObject) { ... } calls with using (lockObject.
From https://blog.cdemi.io/async-waiting-inside-c-sharp-locks/ you can use:
public class MyDbContext
{
static SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1);
public override int SaveChanges()
{
semaphoreSlim.Wait();
try
{
return base.SaveChanges();
}
finally
{
//When the task is ready, release the semaphore. It is vital to ALWAYS release the semaphore when we are ready, or else we will end up with a Semaphore that is forever locked.
//This is why it is important to do the Release within a try...finally clause; program execution may crash or take a different path, this way you are guaranteed execution
semaphoreSlim.Release();
}
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
await semaphoreSlim.WaitAsync(cancellationToken);
try
{
return await base.SaveChangesAsync();
}
finally
{
//When the task is ready, release the semaphore. It is vital to ALWAYS release the semaphore when we are ready, or else we will end up with a Semaphore that is forever locked.
//This is why it is important to do the Release within a try...finally clause; program execution may crash or take a different path, this way you are guaranteed execution
semaphoreSlim.Release();
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With