有什么方法可以称之为吸气剂并始终如一地获得预期结果吗?

时间:2014-01-31 20:06:03

标签: c# multithreading locking race-condition

我需要使用来自第三方API的属性getter,只需访问该getter 有时挂起整个应用程序,有时有效(在dev / debugger中) 。如果我将它部署到生产服务器,主机应用程序有时按预期工作,有时在KERNEL32中死于APPCRASH。

我无法理解一个简单的getter如何冻结或崩溃任何东西,所以我可能这个属性是伪装的方法,并使用我最喜欢的反编译器来查找。

这是我遇到的计划/模式:

internal MyClass() : ISomeInterface
{
    Locker = new object();
}

public object Locker { get; private set; }

private SomeObject _myProperty;
public SomeObject MyProperty
{
    get
    {
        lock (Locker)
        {
            if (_myProperty== null)
                MyProperty = SomeCondition ? SomeMethod() : Something.Else;
            return _myProperty;
        }
    }

    private set
    {
        _myProperty = value;
    }
}

我是否正确地相信,只要_myPropertynull,getter就会调用setter,并且此处存在竞争条件错误,可能导致随机/偶然冻结我正在体验?如何确认/确认此冻结实际上是死锁

什么是正确的实施?我被引导相信如果setter也有一个lock (Locker)(我不喜欢Locker可以公开访问,听起来像是在寻找麻烦),而getter会分配{{1 <},而不是调用setter,它是线程安全的。

最后,有没有一种方法可以调用此getter而无需第三方发送“固定”版本,而且不会冻结?

我试过从主UI线程调用它,我试过从_myProperty /后台线程调用它,我在调用之前尝试了Task,我尝试设置超时Sleep(200) ...无论我做什么,我似乎都无法始终如一地调用getter并可靠地获得预期结果( 工作......有时候)

Task对象是公共的,但它是在Locker中定义的,我只能通过它的界面访问它,当然这不会暴露它。有没有办法可以使用反射从外部获取锁定internal class)并实际让它起作用?或者那个吸气剂完全是borked

2 个答案:

答案 0 :(得分:5)

  

我是否正确相信每当_myProperty为null时,getter会调用setter并且这里存在一个竞争条件错误,可能导致我遇到的随机/偶然冻结?我怎样才能确认/证实这种冻结实际上是一个僵局?

没有。在C#中,lock可重入同一个线程。您可以在Monitor.Enter的文档中看到这一点:

  

同一个线程在没有阻塞的情况下多次调用Enter是合法的;但是,在等待对象的其他线程将解除阻塞之前,必须调用相同数量的Exit调用。


  

不喜欢Locker可公开访问,听起来像是在寻找麻烦

我同意你的意见。这更可能是罪魁祸首。 Locker最公开的事实意味着库中的其他内容锁定了此对象,可能导致死锁。

如果您可以通过Visual Studio Concurrency Profiler运行它,您应该能够在死锁处暂停,并在该时间点看到阻塞的对象。

答案 1 :(得分:2)

  

有没有办法可以使用反射从外部获取锁(Monitor.TryEnter(magicallyObtainedLocker))并实​​际让它工作?

是的,鉴于你可以反编译它,我猜是这样的 1 ;以下“works on my machine”:

  • “ThirdParty”作为DLL
  • 构建在自己独立的项目中
  • “InternalLocker”是不同项目中的控制台程序,它引用了ThirdParty DLL

1 我猜你可以从外面获得锁定;希望但不能预测这是否足以避免僵局。


using System;

namespace ThirdParty
{
    public interface ISomeInterface
    {
        string MyProperty { get; }
    }

    public static class Factory
    {
        public static ISomeInterface create() { return new MyClass(); }
    }

    internal class MyClass : ISomeInterface
    {
        internal MyClass()
        {
            Locker = new object();
        }

        public object Locker { get; private set; }

        private string _myProperty;
        public string MyProperty
        {
            get
            {
                lock (Locker)
                {
                    if (_myProperty == null)
                        MyProperty = "foo";
                    return _myProperty;
                }
            }

            private set
            {
                _myProperty = value;
            }
        }
    }
}

using System;
using System.Linq;

using System.Reflection;
using System.Threading;
using ThirdParty;

namespace InternalLocker
{
    class Program
    {
        static void Main(string[] args)
        {
            test1();
            test2();
        }

        static void test1()
        {
            ISomeInterface thing = Factory.create();
            string foo = thing.MyProperty;
            assert(foo == "foo");
        }

        static void test2()
        {
            ISomeInterface thing = Factory.create();
            // use reflection to find the public property
            Type type = thing.GetType();
            PropertyInfo propertyInfo = type.GetProperty("Locker");
            // get the property value
            object locker = propertyInfo.GetValue(thing, null);
            // use the property value
            if (Monitor.TryEnter(locker))
            {
                string foo = thing.MyProperty;
                Monitor.Exit(locker);
                assert(foo == "foo");
            }
            else
                assert(false);
        }

        static void assert(bool b)
        {
            if (!b)
                throw new Exception();
        }
    }
}