Syncronization of async EF Tasks

.net-4.5 async-await c# entity-framework-6 multithreading

Question

I am working on an Application to processes Company resources. We have a MS SQL Database and are working with EntityFramework 6.1.3 and .Net4.5. Now DbContext has async methods for example

protected Task<int> SaveChangesAsync();

I know that DbContext is NOT Thread safe and I dont want to actually call Async operations with the intend that they will be executed parallel. But I need to free up my GUI thread while polling the database.

Normally you would go and programm with await keywords and "just be carefull" not to call 2 Async operations at the same time BUT the application is supposed to be always up-to-date. Therefor we have a server connection. Now everytime some other user updates the database a polling command is sent to all Applications that are currently working on this database. This polling command could be anytime and would therefor be a racecondition with any user initiated async polling of the database.

What I want to do is queue each Task after the previous one. BUT I dont really know how to or if that even is a good idea. So far I tried working with Task.ContinueWith() but noticed that my Tasks are never starting and therefor I wait forever.

Here would be the code so far
(_lastRunningTask is declared in my DbContext inhereting class and initialized in the constructor with :

_lastRunningTask = Task.Run(() => { Thread.Sleep(1); });

)

private Task _lastRunningTask;

private Task<int> SaveChangesAsyncImpl(CancellationToken cancellationToken)
    {
        return ValidateEntitiesAsyncImpl().ContinueWith(p => SaveChangesAsync(cancellationToken)).Unwrap();
    }

    public override int SaveChanges()
    {

        Task<int> t = _lastRunningTask.ContinueWith(p => SaveChangesAsyncImpl(CancellationToken.None)).Unwrap();

        _lastRunningTask = t;

        return t.Result;
    }
    public override Task<int> SaveChangesAsync()
    {
        return SaveChangesAsync(CancellationToken.None);
    }
    public override Task<int> SaveChangesAsync(CancellationToken cancellationToken)
    {
        Task<int> t = _lastRunningTask.ContinueWith(p => SaveChangesAsyncImpl(cancellationToken)).Unwrap();
        _lastRunningTask = t;
        return t;
    }




    public Task ValidateEntitiesAsync()
    {
        return _lastRunningTask = _lastRunningTask.ContinueWith(p => ValidateEntitiesAsyncImpl()).Unwrap();
    }

    private Task ValidateEntitiesAsyncImpl()
    {
        return Task.Run(() =>
        {
            ChangeTracker.DetectChanges();
            List<DbEntityEntry> AllEntites = new List<DbEntityEntry>(ChangeTracker.Entries());

            try
            {
                foreach (DbEntityEntry entity in AllEntites)
                {
                    if (entity.State == EntityState.Deleted)
                        ((EntityBase)entity.Entity).readyToDelete();
                }
            }
            catch (ErrorException e)
            {
                fireError(e);
                throw e;
            }
            catch (Exception e)
            {
                return;
            }
            return;
        });
    }

    public Task RevertChangesAsync<TEntity>(CancellationToken token) 
        where TEntity : EntityBase
    {
        return _lastRunningTask = _lastRunningTask.ContinueWith(p => RevertChangesAsync<TEntity>(token)).Unwrap();
    }

    private Task RevertChangesAsyncImpl<TEntity>(CancellationToken token) where TEntity : EntityBase
    {
        return Task.Run(() => revertDummy<TEntity>(token));
    }
    private async void revertDummy<TEntity>(CancellationToken token) where TEntity : EntityBase
    {
        ChangeTracker.DetectChanges();
        List<DbEntityEntry<TEntity>> revertEntries = new List<DbEntityEntry<TEntity>>(ChangeTracker.Entries<TEntity>());

        foreach (DbEntityEntry entity in revertEntries)
        {
            await entity.ReloadAsync(token);
        }
    }
1
2
7/30/2015 9:35:55 AM

Popular Answer

I know that DbContext is NOT Thread safe and I dont want to actually call Async operations with the intend that they will be executed parallel.

DbContext is indeed not thread-safe. But, that doesn't mean that you query it concurrently. With async, there is no thread. You don't need to use Task.Run for any of the work you want to do. Take advantage to naturally async API's offered by Entity Framework.

As long as you start a new instance of the DbContext for each query, you are fine.

For example:

private async Task RevertDummyAsync<TEntity>(
    Entity entity, CancellationToken token) where TEntity : EntityBase
{
    using (var context = new SomeDbContext())
    {
        ChangeTracker.DetectChanges();
        List<DbEntityEntry<TEntity>> revertEntries = 
            new List<DbEntityEntry<TEntity>>(ChangeTracker.Entries<TEntity>());

        foreach (DbEntityEntry entity in revertEntries)
        {
            await entity.ReloadAsync(token);
        }
    }
}

And then you can call it on a collection of entities:

var collections = new List<Entity>();
var tasks = collections.Select(x => RevertDummyAsync<Entity>(x));
await Task.WhenAll(tasks);
3
7/30/2015 9:56:14 AM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow