在多线程程序中使用EF的好建议?

时间:2010-07-15 17:49:03

标签: c# .net entity-framework multithreading locking

您是否有一些好的建议在多线程程序中使用EF?

我有两层:

  • 用于读/写到我的数据库的EF层
  • 一个多线程服务,它使用我的实体(读/写)并进行一些计算(我在框架中使用任务并行库)

如何在每个线程中同步我的对象上下文? 你知道一个好的模式让它发挥作用吗?

7 个答案:

答案 0 :(得分:3)

好的建议是 - 只是不要: - ) EF几乎无法在一个线程中生存 - 野兽的本质。

如果您必须使用它,请制作最轻的DTO-s,一旦有数据就关闭OC,重新打包数据,生成线程只是为了进行计算而没有别的,等到它们完成,然后创建另一个OC并将数据转储回DB,协调它等。

如果另一个“主”线程(通过TPL生成N个计算线程的线程)需要知道某个线程何时完成了fire事件,只需在另一个线程中设置一个标志,然后让它的代码检查其中的标志循环并通过创建新的OC进行反应,然后在必要时协调数据。

如果您的情况更简单,您可以对此进行调整 - 关键是您只能设置一个标志,让其他线程在准备就绪时做出反应。这意味着它处于一个稳定的状态,已经完成了一轮任何它正在做的事情并且可以在没有竞争条件的情况下做事。使用交换操作重置标志(一个int)并保留一些定时数据以确保您的线程在一段时间内不再做出反应T - 否则他们可以花一生的时间来查询数据库。

答案 1 :(得分:1)

这就是我实施方案的方式。

var processing= new ConcurrentQueue<int>();


//possible multi threaded enumeration only processed non-queued records
Parallel.ForEach(dataEnumeration, dataItem=>
{
     if(!processing.Contains(dataItem.Id))
     {
         processing.Enqueue(dataItem.Id);

          var myEntityResource = new EntityResource();

          myEntityResource.EntityRecords.Add(new EntityRecord
                                      {
                                        Field1="Value1", 
                                        Field2="Value2"
                                      }
                               );

           SaveContext(myEntityResource);

       var itemIdProcessed = 0;
       processing.TryDequeue(out itemIdProcessed );

     }

}

public void RefreshContext(DbContext context)
    {
        var modifiedEntries = context.ChangeTracker.Entries()
            .Where(e => e.State == EntityState.Modified || e.State == EntityState.Deleted);
        foreach (var modifiedEntry in modifiedEntries)
        {
            modifiedEntry.Reload();
        }
    }

public bool SaveContext(DbContext context,out Exception error, bool reloadContextFirst = true)
    {
        error = null;
        var saved = false;
        try
        {
            if (reloadContextFirst)
                this.RefreshContext(context);
            context.SaveChanges();
            saved = true;
        }
        catch (OptimisticConcurrencyException)
        {
            //retry saving on concurrency error
            if (reloadContextFirst)
                this.RefreshContext(context);
            context.SaveChanges();
            saved = true;
        }
        catch (DbEntityValidationException dbValEx)
        {
            var outputLines = new StringBuilder();
            foreach (var eve in dbValEx.EntityValidationErrors)
            {
                outputLines.AppendFormat("{0}: Entity of type \"{1}\" in state \"{2}\" has the following validation errors:",
                    DateTime.Now, eve.Entry.Entity.GetType().Name, eve.Entry.State);
                foreach (var ve in eve.ValidationErrors)
                {
                    outputLines.AppendFormat("- Property: \"{0}\", Error: \"{1}\"", ve.PropertyName, ve.ErrorMessage);
                }
            }

            throw new DbEntityValidationException(string.Format("Validation errors\r\n{0}", outputLines.ToString()), dbValEx);
        }
        catch (Exception ex)
        {
            error = new Exception("Error saving changes to the database.", ex);
        }
        return saved;
    }

答案 2 :(得分:0)

我认为Craig对你的应用程序可能是正确的,不需要线程..但是你可能会在模型中寻找ConcurrencyCheck的用法,以确保你不“覆盖”你的更改

答案 3 :(得分:0)

我不知道您的应用程序实际上有多少是数字运算。如果速度是使用多线程的动机那么它可能会退后一步并收集关于下一个瓶子的位置的数据。

在很多情况下,我发现使用数据库服务器的应用程序中的限制因素是存储的I / O系统的速度。例如,硬盘驱动器盘的速度及其配置会产生巨大影响。具有7,200 RPM的单个硬盘驱动器每秒可处理大约60个事务(球场数据取决于许多因素)。

所以我的建议是首先衡量并找出下一瓶的位置。有可能你甚至不需要线程。这将使代码更容易维护,并且质量可能更高。

答案 4 :(得分:0)

“我如何在每个线程中同步我的对象上下文?” 这将是艰难的。首先,SP或DB查询可以有并行执行计划。因此,如果你在对象上下文中也有并行性,你必须手动确保你有足够的隔离,但只要你没有锁定太久以至于导致死锁。

所以我想说不需要这样做。

但这可能不是你想要的答案。那么你能用更多的线程解释你想要实现的目标吗?它是更多计算绑定还是IO绑定。如果它是IO绑定长期运行的操作,那么请看看Jeff Richter的APM。

答案 5 :(得分:0)

我认为你的问题更多的是关于线程之间的同步,EF在这里是无关紧要的。如果我理解正确,你想在主线程执行某些操作时通知来自一个组的线程 - 在这种情况下是“SaveChanges()”操作。这里的线程类似于客户端 - 服务器应用程序,其中一个线程是服务器而其他线程是客户端,您希望客户端线程对服务器活动做出反应。

有人注意到你可能不需要线程,但让它保持原样。

只要你要为每个线程使用单独的OC,就不用担心死锁。

我还假设您的客户端线程在某种循环中是长时间运行的线程。如果您希望在客户端线程上执行代码,则无法使用C#事件。

class ClientThread {
public bool SomethingHasChanged;

  public MainLoop()
  {
    Loop {
      if (SomethingHasChanged)
      { 
        refresh();
        SomethingHasChanged = false;
      }

      // your business logic here


    } // End Loop
  }
}

现在的问题是如何在所有客户端线程中设置标志?您可以在主线程中保留对客户端线程的引用并循环遍历它们并将所有标志设置为true。

答案 6 :(得分:0)

当我使用EF时,我只有一个ObjectContext,我同步了所有访问。

这不太理想。您的数据库层实际上是单线程的。但是,它确实在多线程环境中保持线程安全。在我的情况下,繁重的计算根本不在数据库代码中 - 这是一个游戏服务器,因此游戏逻辑当然是主要的资源需求。因此,我对多线程数据库层没有任何特殊需求。