System.Threading.Monitor.Enter(SyncRoot) 未按预期工作,为什么?

问题描述 投票:0回答:1

我已经使用 BlockingEnumerator 类完成了 ObservableCollection 多线程“ObsCollMt”,当使用“ForEach”枚举项目时,该类应该锁定底层 _list。

我修复了我的代码,但我不明白为什么它以新方式工作以及为什么它不以旧方式工作。有人能给我一个清晰的解释吗?

我包含了错误中隐含的两部分代码。在评论中:“NOT WORKING CODE”(旧方式)代码不起作用,因为它从不锁定“SyncRoot”,为什么?

ObsCollMt 获取阻塞迭代器的代码:

List<T> _list = new List<T>();
public object SyncRoot { get; } = new object();

// ******************************************************************
/// <summary>
/// This iterator block the collection for all the time you iterate in it. 
/// For long job, it is preferable to use a copy of items.
/// </summary>
/// <returns></returns>
public virtual IEnumerator<T> GetEnumerator()
{
    // WORKING CODE (lock the _list) (New way)
    System.Threading.Monitor.Enter(SyncRoot);
    return new BlockingIteratorWithActionBlockUnblock<T>(_list.GetEnumerator(), () => { }, () => System.Threading.Monitor.Exit(SyncRoot));

    // NOT WORKING CODE (the _list is never blocked) (Old way)
    //return new BlockingIteratorWithActionBlockUnblock<T>(_list.GetEnumerator(), () => System.Threading.Monitor.Enter(SyncRoot), () => System.Threading.Monitor.Exit(SyncRoot));
}

阻塞迭代器代码:

using System;
using System.Collections;
using System.Collections.Generic;

namespace General.Collection
{
    public class BlockingIteratorWithActionBlockUnblock<T> : IDisposable, IEnumerator<T>
    {
        private IEnumerator<T> _iEnumerator = null;
        private Action _actionUnlock = null;

         public BlockingIteratorWithActionBlockUnblock(IEnumerator<T> iEnumerator, Action actionLock, Action actionUnlock)
        {
            if (iEnumerator == null) { throw new ArgumentException(nameof(iEnumerator)); }
            if (actionLock == null) { throw new ArgumentException(nameof(actionLock)); }
            if (actionUnlock == null) { throw new ArgumentException(nameof(actionUnlock)); }

            _iEnumerator = iEnumerator;
            _actionUnlock = actionUnlock;

            actionLock();
        }

        public T Current
        {
            get { return _iEnumerator.Current; }
        }

        object IEnumerator.Current
        {
            get { return _iEnumerator.Current; }
        }

        public bool MoveNext()
        {
            return _iEnumerator.MoveNext();
        }

        public void Reset()
        {
            _iEnumerator.Reset();
        }

        private bool _disposed = false;

        public void Dispose()
        {
            Dispose(true);

            // Use SupressFinalize in case a subclass 
            // of this type implements a finalizer.
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            // If you need thread safety, use a lock around these  
            // operations, as well as in your methods that use the resource. 
            if (!_disposed)
            {
                if (disposing)
                {
                    _actionUnlock.Invoke();

                    var dispoable = _iEnumerator as IDisposable;
                    if (dispoable != null)
                    {
                        dispoable.Dispose();
                        _iEnumerator = null;
                    }
                }

                // Indicate that the instance has been disposed.
                _disposed = true;
            }
        }
    }
}
c# multithreading synchronization locking idisposable
1个回答
0
投票

代码不起作用,您尝试在提供给 BlockingIteratorWithActionBlockUnblock 的 lambda 中使用 System.Threading.Monitor.Enter(SyncRoot) 锁定 SyncRoot。这里的问题是 lambda 本身是在迭代器设置期间执行的,而不是在实际迭代期间执行的。因此,锁定和解锁操作发生在迭代上下文之外,导致锁定无效。

在工作版本中,您在创建迭代器之前锁定 SyncRoot,并在迭代器完成后解锁它。这确保了锁定和解锁操作与迭代过程正确关联。

总而言之,关键的区别在于锁定和解锁操作发生的时间。在工作版本中,锁在迭代开始之前应用,并在迭代完成后释放,确保整个迭代都受到锁的保护。在不工作的版本中,锁定和解锁操作被安排为迭代器设置的一部分,而不是在实际迭代期间,从而使锁定无效。

© www.soinside.com 2019 - 2024. All rights reserved.