实体框架并发刷新和更新

5
我已经编写了一个.NET + EF应用程序。在单个线程上一切正常。但在多个线程上就不同了。
在我的EF对象中,我有一个整数计数器属性。该属性被标记为“并发模式=固定”。基本上,我正在尝试在几个线程上更新此计数器。就像这样的操作:
this.MyCounter -= 1;

因为其并发模式已更改为“固定”,所以当我试图更新已更改的属性时,会抛出一个OptimisticConcurrencyException异常。

为了解决这个并发问题,我正在使用以下代码:

while (true)
{
    try
    {
        this.UsageAmount -= 1; // Change the local EF object value and call SaveChanges().
        break;
    }
    catch (OptimisticConcurrencyException)
    {
        Logger.Output(LoggerLevel.Trace, this, "concurrency conflict detected.");
        EntityContainer.Instance.Entities.Refresh(RefreshMode.StoreWins, this.InnerObject);
    }
}

这段代码的结果是一个无限(或者看上去像是)循环。每次调用this.UsageAmount -= 1都会抛出一个OptimisticConcurrencyException,导致循环再次运行。
我的EntityContainer.Instance.Entities是一个单例类,为每个线程提供一个EF上下文。这意味着每个线程都有一个唯一的上下文。以下是代码:
public sealed class EntityContainer
    {
        #region Singlethon Implemantation
        private static Dictionary<Thread, EntityContainer> _instance = new Dictionary<Thread,EntityContainer> ();
        private static object syncRoot = new Object();
        public static EntityContainer Instance
        {
            get
            {
                if (!_instance.ContainsKey(Thread.CurrentThread))
                {
                    lock (syncRoot)
                    {
                        if (!_instance.ContainsKey(Thread.CurrentThread))
                            _instance.Add(Thread.CurrentThread, new EntityContainer());
                    }
                }
                return _instance[Thread.CurrentThread];
            }
        }
        private EntityContainer()
        {
            Entities = new anticopyEntities2();
        }
        #endregion

        anticopyEntities2 _entities;
        public anticopyEntities2 Entities
        {
            get
            {
                //return new anticopyEntities2();
                return _entities;
            }
            private set
            {
                _entities = value;
            }
        }
    }

顺便提一下,在调用Entities.Refresh方法之后——看起来它正在工作(对象状态为“Unchanged”,属性值正好与数据库中存在的相同)。

我该如何解决这个并发问题?


1
你能否发布一下你提到的单例类 EntityContainer.Instance.Entities 的代码?- 只需要提供“每个线程一个EF上下文”的部分。 - lukiffer
EntityContainer.Instance.Entities 代码被插入到帖子中。 - No1Lives4Ever
当你说每个调用都会抛出异常时,这可能是因为一些线程被阻止更新,因为它们每次都被数据库击败了吗?数据库中的值实际上是否在改变? - anton.burger
1
这段代码一直运行良好...但是,有时会进入“问题模式”,每次尝试更改值时都会抛出OptimisticConcurrencyException异常。我不知道为什么它会进入此模式或何时进入。对我来说似乎相当随机。当我检查是否有另一个线程正在尝试执行相同的操作时 - 对我来说似乎没有另一个线程正在运行(但是,不能保证)。 - No1Lives4Ever
那么当它进入“问题模式”时,数据库值根本不会改变,而且似乎每个线程都在每次抛出异常?只是为了明确,如果数据库值仍在更改,则某个地方肯定有一个线程成功进行更新? - anton.burger
在“问题模式”下 - 数据库值根本没有改变。我直接通过MS管理工作室查看表,并且该值未更改。因此,我假设没有后台线程成功更新此值。 - No1Lives4Ever
1个回答

1

我在为多实例Azure WebRole编写的一些代码中解决了这个问题,通过使用一个信号量并将其保存在我的数据库中。以下是我用于获取信号量的代码。我不得不添加一些额外的代码来处理发生在竞争实例之间的竞争条件。如果我的信号量因某些错误而被锁定,我还添加了时间释放。

        var semaphore = SemaphoreRepository.FetchMySemaphore(myContext);
        var past = DateTime.UtcNow.AddHours(-1);

        //check lock, break if in use.  Ignor if the lock is stale.
        if (semaphore == null || (semaphore.InUse && (semaphore.ModifiedDate.HasValue && semaphore.ModifiedDate > past)))
        {
            return;
        }

        //Update semaphore to hold lock
        try
        {
            semaphore.InUse = true;
            semaphore.OverrideAuditing = true;
            semaphore.ModifiedDate = DateTime.UtcNow;
            myContext.Entry(semaphore).State = EntityState.Modified;
            myContext.SaveChanges();
        }
        catch (DbUpdateConcurrencyException)
        {
            //concurrency exception handeling another thread beat us in the race.  exit
            return;
        }
        catch (DBConcurrencyException)
        {
            return;
        }

        //Do work here ...  

我的信号量模型长这样:

using System.ComponentModel.DataAnnotations;

public class Semaphore : MyEntityBase //contains audit properties
{

    [Required]
    [ConcurrencyCheck]
    public bool InUse { get; set; }

    public string Description { get; set; }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接