【问题标题】:Syncronization of async EF Tasks异步 EF 任务的同步
【发布时间】:2015-07-30 17:56:14
【问题描述】:

我正在开发一个应用程序来处理公司资源。我们有一个 MS SQL 数据库,并且正在使用 EntityFramework 6.1.3 和 .Net4.5。 现在 DbContext 有异步方法,例如

protected Task<int> SaveChangesAsync();

我知道 DbContext 不是线程安全的,我不想实际调用异步操作以使它们并行执行。但是我需要在轮询数据库时释放我的 GUI 线程。

通常您会使用 await 关键字进行编程,并且“请注意”不要同时调用 2 个异步操作,但应用程序应该始终是最新的。因此,我们有一个服务器连接。现在,每次其他用户更新数据库时,都会向当前正在处理该数据库的所有应用程序发送轮询命令。此轮询命令可以在任何时间执行,因此对于任何用户发起的数据库异步轮询都是一种竞争条件。

我想要做的是将每个任务排在前一个任务之后。但我真的不知道该怎么做,或者这是否是个好主意。 到目前为止,我尝试使用 Task.ContinueWith(),但注意到我的任务永远不会启动,因此我永远等待。

这是目前为止的代码
(_lastRunningTask 在我的 DbContext 继承类中声明并在构造函数中初始化:

_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);
        }
    }

【问题讨论】:

  • 我有类似的问题。最终使用 BackGroundworker 进行 SQL 数据库查询,因此我的 Windows 窗体没有锁定。

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


【解决方案1】:

我知道 DbContext 不是线程安全的,我实际上不想这样做 调用异步操作,意图执行它们 并行。

DbContext 确实不是线程安全的。但是,这并不意味着您同时查询它。与async、there is no thread。您不需要使用Task.Run 来完成您想做的任何工作。利用 Entity Framework 提供的自然异步 API。

只要为每个查询启动一个 DbContext 的新实例,就可以了。

例如:

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);
        }
    }
}

然后你可以在实体集合上调用它:

var collections = new List<Entity>();
var tasks = collections.Select(x => RevertDummyAsync<Entity>(x));
await Task.WhenAll(tasks);

【讨论】:

  • 好的,我已经看过几次了。我想我只是不明白这一点:我在我的上下文对象上绑定了我所有的视图,如果我创建我的上下文的一个新对象并加载一个 DbSet,那么在我的脑海中我的 DbContext 的第一个创建的对象不会得到这个“更新”因此我的观点仍然过时???这是正确的,如果不是我错在哪里???
  • @D4rthB4n3 您是否尝试过了解async-await 的工作原理?
  • 是和不是。我确实读了一些,但显然还不够而且我没有得到更多的是,这总是一个新的上下文,只是把评论搞砸了
  • 在我看来,我的 DbContext 的第一个创建对象不会得到这个“更新”,因此我的观点仍然过时只要你正确地 SaveChanges 和 Dispose您的DbContext,新创建的将加载新值。
  • 是的,新的将加载,但加载的实体在新上下文的 DbSet 属性中,对吗?保存更改和恢复方法在类 ToolDatabase 中:DbContext 所以在这个类中我会实例化同一个类的新对象做一个加载方法,然后在使用块的末尾处理这个新创建的对象。现在,如果我新加载的实体可以通过“旧”上下文 DbSet 访问,那么就可以了,但我不明白为什么会这样。 (对不起,如果我不清楚我仍然不太了解 DbContext 和 DbSet 是如何协同工作的)
猜你喜欢
  • 1970-01-01
  • 2015-09-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多