4

在我的 MVC 3 C# 应用程序中,我有一些静态对象,我希望一次可用于一个请求。只能通过方法访问它,但我希望在调用其方法之间保持锁定。

调用只会在控制器中完成,通常会有一两个锁定的代码块。

起初我想公开一些静态公共对象并像使用它一样简单地使用它

lock(MyClass.lockObject)
{
 MyClass.doStuff();
 MyClass.doStuff2();
}

,但我发现它容易出错,因为我可能忘记将它锁定在某个地方。我想知道Monitor.Enter()在构造函数和Monitor.Exit()Dispose 方法中使用它是否是正确的方法,然后将我的方法更改为非静态方法?说,类似:

public class MyClass:IDisposable
{
    static protected object _locker = new object();
    protected bool isDisposed = false;

    public MyClass()
    {
        Monitor.Enter(_locker);
    }

    public void Dispose()
    {
        if (!isDisposed)
        {
            Monitor.Exit(_locker);
            GC.SuppressFinalize(this);
            isDisposed = true;
        }
    }

    ~SPInstances()
    {
        Dispose();
    }

    public void doStuff()
    {
        if(isDisposed) throw new ObjectDisposedException();
        // do stuff here, etc.
    }
}

然后我可以将其用作:

using(MyClass myinstance = new MyClass())
{
    myInstance.doStuff();
    myInstance.doStuff2();
}

然后,即使我在使用时忘记包装代码,它仍然会锁定,并且在垃圾收集期间它有可能会被解锁......

我不精通C#,有时会忽略某些方面,并且线程以后调试起来也不容易,所以我想知道我是否走上了正轨。这是实现我的目标的正确方法吗?

编辑:

扩展Master Morality的想法,这种方式会更好(我简化了一点,因为我只需要一个资源实例)?

public class MyClass
{
    static protected readonly MyResourceType _myResourceStatic = new MyResourceType();
    static public void DoWork(Action<MyClass> action)
    {
        lock(_myResource)
        {
            action(new MyClass(_myResource));
        }        
    }

    protected MyClass(MyResourceType myResource)
    {
        _myResource = myResource;
    }
    protected readonly _myResource;

    public void DoFirstThing() { ... }
    public void DoSecondThing(){ ... }
}

MyClass.DoWork(x => 
{
    x.DoFirstThing();
    // do unrelated stuff
    x.DoSecondThing();
});
4

5 回答 5

2

恕我直言,最好lock在自己的方法中。这样,另一个程序员,或者以后的你自己,在调用方法之前不必记住lock,这很简单。

public class MyClass
{
    private static readonly object _gate = new object();

    /* something that can only be accessed by one thread at a time...*/
    private static MyResourceType MyResource = new MyResourceType();

    public void DoSomething()
    {
         lock(_gate)
         {
            /* do something with MyResource, just make sure you
               DO NOT call another method that locks the gate
               i.e. this.DoSomethingElse(), in those situations,
               you can take the logic from DoSomethingElse() and
               toss it in a private method i.e. _DoSomethingElse().
             */
         }
    }

    private void _DoSomethingElse()
    {
        /* do something else */
    }

    public void DoSomethingElse()
    {
         lock(_gate)
         {
             _DoSomethingElse();
         }
     }
}

那天晚些时候...

var myClass = new MyClass();
myClass.DoSomething();

如果您希望能够使用锁调用多个方法,则可以使用 lambda 来实现,并且为了真正安全,将其包装在辅助类中。

public class MyClass
{
    public MyResourceType MyResource { get; set; }
    public void DoFirstThing() { ... }
    public void DoSecondThing(){ ... }
}

public class MyClassHelper
{
    private static readonly object _gate = new Object();
    private static MyResourceType MyResource = new MyResourceType();

    private MyClass _myClass = new MyClass();        

    public void DoWork(Action<MyClass> action)
    {
         lock(_gate)
         {
             _myClass.MyResource = MyResource;
             action(_myClass);
             _myClass.MyResource = null;
         }
    }
}

...

var myClassHelper = new MyClassHelper();
myClassHelper.DoWork(x => 
    {
        x.DoFirstThing();
        x.DoSecondThing();
    });
于 2012-08-02T15:22:50.713 回答
1

锁定比直接使用 Monitor.Enter 和 Exit 更容易,更不容易出错。

从您的示例中不清楚您要同步什么。

Monitor.Enter 在构造函数中并在 Dispose 中退出不是一个好主意。 如果无法正确构造类,则必须处理c'tor 中的所有异常并调用 Exit。实例被锁定是没有意义的——这本质上意味着通过锁定 c'tor。您可能想查看 Synchronized 属性;但我不认为这是真的推荐。

于 2012-08-02T14:21:13.120 回答
1

立即执行来自其他对象对您的静态对象的请求是否至关重要?如果你的静态对象维护一个它自己工作的队列,你可以通过线程隔离实现互斥。在来自另一个对象的调用中,请求的工作被放置在队列中,而在一个单独的线程中,静态对象正在通过队列工作(请注意,需要对队列进行互斥访问!)执行请求。

您可以让调用对象阻塞在将工作添加到队列的方法中,直到收到静态对象的通知,或者提供回调接口以允许静态对象通知调用对象他们的工作已完成。

于 2012-08-02T14:29:06.060 回答
1

从您的示例来看,您并不清楚您要做什么。作为良好的编程习惯,最好让每个单独的方法获得锁并在完成临界区时释放它。在您的情况下,它将是:

void doStuff()
{
    if(isDisposed) throw new ObjectDisposedException();
    // do stuff here, etc.
    lock(_locker) { 
       // enter critical section here 
    }
    // continue to do other stuff
}

void doStuff2()
{
    if(isDisposed) throw new ObjectDisposedException();
    // do stuff here, etc.
    lock(_locker) { 
       // enter critical section here 
    }
    // continue to do other stuff
}

现在 lock 是使用 Monitor 类的快捷版本。并且实际上是这样翻译的:

bool getLock = false;
try {
  Monitor.Enter(locker, ref getLock);
  // do stuff here
}
finally {
  if(getLock) {
    Monitor.Exit(locker);
  }
}

这将使您可以更好地控制对象和内部表示的状态,就好像发生了错误一样,您可以恢复到对象的先前状态。

于 2012-08-02T15:07:37.703 回答
1

如果调用的组合不多,您可以将 dostuff 和 doStuff2 设为私有,并在您的类中放置一个带锁的包装函数

static public void doStuffs()
{
    lock (lockObject)
    {
        doStuff();
        doStuff2();
    }
}
于 2012-08-02T15:31:16.530 回答