C# 在使用拦截器时,如何避免DevForce中的这种死锁

C# 在使用拦截器时,如何避免DevForce中的这种死锁,c#,multithreading,deadlock,devforce,C#,Multithreading,Deadlock,Devforce,我们最近开始采用严重依赖DevForce的业务逻辑,并通过webapi将其公开。我们非常小心地避免线程问题,确保每个请求都有自己的一组实体、自己的EntityManager,等等。但是,当存在大量并发请求时,我们开始注意到逻辑死锁(在.net代码中,而不是在SQL中) 我已经找到了由属性interceptors完成的锁定问题。我们非常广泛地使用它们,并且在某些情况下,一个属性(属性A)上的拦截器将设置另一个属性(属性B),但反过来也是正确的(设置B也将设置A)。其中一些情况的确切原因很复杂,但基

我们最近开始采用严重依赖DevForce的业务逻辑,并通过webapi将其公开。我们非常小心地避免线程问题,确保每个请求都有自己的一组实体、自己的
EntityManager
,等等。但是,当存在大量并发请求时,我们开始注意到逻辑死锁(在.net代码中,而不是在SQL中)

我已经找到了由
属性interceptors
完成的锁定问题。我们非常广泛地使用它们,并且在某些情况下,一个属性(属性A)上的拦截器将设置另一个属性(属性B),但反过来也是正确的(设置B也将设置A)。其中一些情况的确切原因很复杂,但基本思想是我们有一些想要保持同步的属性。似乎
属性interceptor
逻辑中存在锁定,因此我们可以很容易地遇到死锁,因为这些锁定的顺序可能会有所不同

我在下面创建了一个简单的可复制案例,涉及一个只有两个属性的实体。一个是整数属性,另一个是字符串属性。我预先设置了逻辑,以使这两者保持同步。在一次设置一个属性的简单情况下,一切正常。但是,由于我们处理的是web api,因此并行执行是非常常见的。如果我们得到一个碰巧设置
IntValue
的请求和另一个碰巧设置
StringValue
的请求,我们将陷入死锁。即使我们在两个不同的
EntityManager
s中讨论两个不同的实体,这也是事实。从我们的角度来看,我们正在以线程安全的方式做每件事,但是DevForce有一些非常长寿命的锁,我们知道这些锁可能是危险的

下面是代码,希望能解释一些事情。请记住,我们的实际代码要复杂得多,但基本死锁是相同的:

public static void ReproduceDeadlock()
{
    var e1 = new MyEntity();
    var e2 = new MyEntity();

    //This works - settings fields one at a time is fine
    e1.IntValue = 1;
    e2.StringValue = "2";

    //But if we introduce some concurrency, we'll become deadlocked
    Task.Run(() =>
    {
        //Wait a bit so e1.IntValue has a chance to start
        Thread.Sleep(1000);

        e2.StringValue = "22";
    });

    e1.IntValue = 11;

    //Execution will never make it hear...setting the IntValue will never complete
}

public class MyEntity : Entity
{
    [BeforeSet("StringValue")]
    public void BeforeSetStringValue(PropertyInterceptorArgs<MyEntity, string> args)
    {
        //When the string is set, 'sync' it to the IntValue property
        IntValue = int.Parse(args.Value);
    }

    [BeforeSet("IntValue")]
    public void BeforeSetIntValue(PropertyInterceptorArgs<MyEntity, int> args)
    {
        //When the int is set, 'sync' it to the StringValue property

        //Introduce a delay so the deadlock will obviously happen.  In our real app, we don't have
        //  a Thread.Sleep() but we do have non-trivial logic that can cause just enough delay for the deadlock
        //  to happen sometimes
        Thread.Sleep(2000);
        StringValue = args.Value.ToString();
    }

    #region PropertyMetadata stuff

    public class PropertyMetadata
    {
        public static readonly DataEntityProperty<MyEntity, string> StringValue =
            new DataEntityProperty<MyEntity, string>("StringValue", true, false,
                ConcurrencyStrategy.None, false, null,
                false);

        public static readonly DataEntityProperty<MyEntity, int> IntValue =
            new DataEntityProperty<MyEntity, int>("IntValue", true, false,
                ConcurrencyStrategy.None, false, null,
                false);
    }

    public string StringValue
    {
        get { return PropertyMetadata.StringValue.GetValue(this); }
        set { PropertyMetadata.StringValue.SetValue(this, value); }
    }

    public int IntValue
    {
        get { return PropertyMetadata.IntValue.GetValue(this); }
        set { PropertyMetadata.IntValue.SetValue(this, value); }
    }

    #endregion
}
publicstaticvoid reproductedeadlock()
{
var e1=新的MyEntity();
var e2=新的MyEntity();
//这是可行的-设置字段一次一个很好
e1.IntValue=1;
e2.StringValue=“2”;
//但是如果我们引入一些并发性,我们就会陷入僵局
Task.Run(()=>
{
//请稍等,以便e1.IntValue有机会启动
睡眠(1000);
e2.StringValue=“22”;
});
e1.IntValue=11;
//执行永远不会让它听到…设置IntValue永远不会完成
}
公共类MyEntity:实体
{
[在设置之前(“StringValue”)]
public void beforesettringvalue(PropertyInterceptorArgs args args)
{
//设置字符串后,将其“同步”到IntValue属性
IntValue=int.Parse(args.Value);
}
[在设置之前(“IntValue”)]
public void BeforeSetIntValue(PropertyInterceptorArgs args)
{
//设置int后,将其“同步”到StringValue属性
//引入延迟,使死锁明显发生。在我们真正的应用程序中,我们没有
//一个Thread.Sleep(),但我们确实有非平凡的逻辑,可能会导致死锁的延迟
//有时发生
《睡眠》(2000年);
StringValue=args.Value.ToString();
}
#区域属性元数据
公共类属性元数据
{
公共静态只读DataEntityProperty StringValue=
新DataEntityProperty(“StringValue”),true、false,
并发策略。无,false,null,
假);
公共静态只读DataEntityProperty IntValue=
新DataEntityProperty(“IntValue”),true、false,
并发策略。无,false,null,
假);
}
公共字符串字符串值
{
获取{return PropertyMetadata.StringValue.GetValue(this);}
set{PropertyMetadata.StringValue.SetValue(this,value);}
}
公共int值
{
获取{return PropertyMetadata.IntValue.GetValue(this);}
set{PropertyMetadata.IntValue.SetValue(this,value);}
}
#端区
}

}斯蒂芬,也许我确实为你找到了解决办法。在拦截器操作中,可以使用SetValueRaw将值同步到另一个属性,并避免通过其拦截器(和验证)。该方法可在公共IStructuralObject接口上使用,尽管该接口的文档仅用于内部使用,但我们不打算对其进行更改。EntityAspect和ComplexSpect类都实现了这个接口

因此,您的示例如下所示:

[BeforeSet("StringValue")]
public void BeforeSetStringValue(PropertyInterceptorArgs<MyEntity, string> args)
{
    //When the string is set, 'sync' it to the IntValue property
    (this.EntityAspect as IStructuralObject).SetValueRaw(PropertyMetadata.IntValue, int.Parse(args.Value));
}

[BeforeSet("IntValue")]
public void BeforeSetIntValue(PropertyInterceptorArgs<MyEntity, int> args)
{
    //When the int is set, 'sync' it to the StringValue property

    //Introduce a delay so the deadlock will obviously happen.  In our real app, we don't have
    //  a Thread.Sleep() but we do have non-trivial logic that can cause just enough delay for the deadlock
    //  to happen sometimes
    Thread.Sleep(2000);

    (this.EntityAspect as IStructuralObject).SetValueRaw(PropertyMetadata.StringValue, args.Value.ToString());
}
然后在这些紧密耦合的拦截器操作中调用:

SetValueWithVerification(this.EntityAspect, PropertyMetadata.StringValue, args.Value.ToString());

我要过几天才能调查这件事。PropertyInterceptorManager是单例的,因此属性拦截器不是EntityManager的本地拦截器。每个PropertyInterceptor实例在执行时都会锁定以确保线程安全,但我想知道这里发生的事情是否发生在PropertyInterceptorManager的发现/初始化阶段。这通常是延迟发生的,但可以通过调用PropertyInterceptorManager.CurrentInstance.DiscoverInterceptorsFromAttributes()强制执行。好的,正如您所发现的,死锁确实发生在执行拦截器操作时,而不是在发现/初始化时。看起来我们需要在PI执行期间使用线程局部变量,而不是锁定。那太好了。线程本地似乎是一个很好的解决方案。我猜我现在真的没有什么解决办法了?我试了几件事,但似乎都不管用。您是否粗略估计了该修复程序何时可用?我们在PropertyInterceptor类中遇到了线程局部变量的性能问题,因此解决方案可能没有这么简单。修复程序可能会在6月中旬提供,但如果您需要它,请尽快让我知道,因为我也想不出任何解决方法
SetValueWithVerification(this.EntityAspect, PropertyMetadata.StringValue, args.Value.ToString());