使用相同的锁修改锁内部的对象

时间:2011-01-14 19:10:00

标签: c# multithreading locking

我有一个由一组工作项组成的工作对象。每个作业都有自己的WatcherClass与之关联,每隔一段时间检查一次数据库,看是否需要取消执行。它可以在工作流程的任何迭代中取消。如果它被取消,从foreach块运行的任何线程都将传播取消并正常退出。

我的观察程序代码中是否存在可能造成死锁的问题?我试图只允许一个线程通过使用Timer.Change(Timeout.Infinite,Timeout.Infinite)来处理定时器回调,但是我在锁定语句中更改WatcherClass.Job的事实会破坏锁定的目的(因为我在同一个锁对象中为_Job包装了相同的get / set)?代码似乎工作正常,但我知道这并不是真的有任何迹象。

主线程中的代码看起来与此类似:

using (WatcherClass watcher = new WatcherClass())
{
    watcher.CancelTokenSource = new CancellationTokenSource();
    watcher.Start();
    foreach (SomeJob job in worksflow.Jobs)
    { 
        watcher.Job = job;
        //Do some stuff async
        //Do some more stuff async
    }

}

public class WatcherClass : IDisposable
{
    private System.Threading.Timer _WatcherTimer;
    private readonly object locker = new object();
    private bool _Disposed = false;
    private SomeJob _Job;

    public SomeJob Job
    {
        get
        {
            lock (locker)
            {
                return _Job;
            }
        }
        set
        {
            lock (locker)
            {
                _Job= value;
            }
        }
    }

    public System.Threading.Task.CancellationTokenSource 
        CancelToken { get; set; }

    public WatcherClass()
    {
        _WatcherTimer = new Timer(new TimerCallback(DoCheck), null, 
            Timeout.Infinite, Timeout.Infinite);
    }

    public void Start()
    {
        _WatcherTimer.Change(30000, Timeout.Infinite);
    }

    public void DoCheck(object state)
    {

        lock (locker)
        {

            if (_Disposed || this.CancelToken.IsCancellationRequested)
                return;

            _WatcherTimer.Change(Timeout.Infinite, Timeout.Infinite);

            //Check database to see if task is cancelled
            if (cancelled)
            {
                this.CancelToken.Cancel();
                _Job.CancelResult = CancelResult.CanceledByUser;
                _Job.SomeOtherProperty = true;
            }
            else
            {
                //Safe to continue
                _WatcherTimer.Change(30000, Timeout.Infinite);
            }
        }

    }

    public void Dispose(bool disposing)
    {
        lock (locker)
        {
            if (disposing)
            {
                if (_WatcherTimer != null)
                    _WatcherTimer.Dispose();

            }
            _Disposed = true;
        }
    }
}

1 个答案:

答案 0 :(得分:1)

您在Task属性和DoCheck函数中获取的锁仅保护对WatcherClass的内部_task字段的访问。在DoCheck中,您还要修改_task对象本身的属性。锁定不会阻止其他任何人同时从其他线程修改任务对象的字段。

如果在您的应用程序中任务对象仅由DoCheck操纵,那么您可能没问题。如果任务对象可能被DoCheck以外的代码操纵,那么您可能会遇到问题。

另请注意,您创建的每个额外锁定都是死锁的额外机会。如果始终以特定顺序获取多个锁,则它们可以无死锁。如果代码流允许在某些情况下在锁B之前获取锁A,或者在其他情况下在锁A之前锁定B,那么您将面临严重的死锁风险。 (线程1锁定A,尝试锁定B,而线程2锁定B并尝试锁定A =>死锁)

在WatcherClass的情况下,如果你将要有多个watcherclass实例,每个实例都有自己的锁,请注意不要进行外部调用(或触发事件),这些调用最终可能会尝试获取其他watcherclass实例中的锁。这是一个等待发生的AB / BA死锁。