C# ReaderWriterLock在ServiceBehavior构造函数中不工作

C# ReaderWriterLock在ServiceBehavior构造函数中不工作,c#,multithreading,wcf,readerwriterlock,wcf-instancing,C#,Multithreading,Wcf,Readerwriterlock,Wcf Instancing,我有一个WCF服务,其中InstanceContextMode是Single而ConcurrencyMode是Multiple。其目的是在实例化时创建一个值缓存,而不占用不依赖于缓存创建的其他服务调用 这样,只有尝试在\u classificationsCacheLock上获取读锁的方法才需要等待,直到填充classificationsCache的值(classificationsCacheLock.isWriterLockHold=false) 然而,问题是,尽管在任务线程中获得了写锁,调用W

我有一个WCF服务,其中
InstanceContextMode
Single
ConcurrencyMode
Multiple
。其目的是在实例化时创建一个值缓存,而不占用不依赖于缓存创建的其他服务调用

这样,只有尝试在
\u classificationsCacheLock
上获取读锁的方法才需要等待,直到填充
classificationsCache
的值(
classificationsCacheLock.isWriterLockHold=false

然而,问题是,尽管在任务线程中获得了写锁,调用WCF仍继续服务,以响应对服务方法
GetFOIRequestClassificationsList()
的调用,结果是
\u classificationsCacheLock.isWriterLockHold
,而它应该为真

这是
WCF
实例的奇怪行为,还是这是我基本上错过了一个技巧

我尝试在构造函数的线程上下文(安全选项)和生成的任务线程上下文(这可能会在
WCF
之间引入竞争)中获取写锁,然后调用
GetFOIRequestClassificationsList()调用
函数比调用
classificationsCacheLock.AcquireWriterLock(Timeout.Infinite);
更快,但两者都会导致
classificationsCacheLock.IsWriterLockHold
false
,尽管通过使用thread.sleep阻止了任何竞争条件,在各个线程的代码块中适当地交错

[ServiceBehavior(Namespace = Namespaces.MyNamespace,
    ConcurrencyMode = ConcurrencyMode.Multiple,
InstanceContextMode = InstanceContextMode.Single)]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class MyService : IMyService
{
    private static NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();

    private List<string> _classificationsCache;

    private ReaderWriterLock _classificationsCacheLock;

    public MyService()
    {
        try
        {
            _classificationsCacheLock = new ReaderWriterLock();
            LoadCache();
        }
        catch (Exception ex)
        {
            _logger.Error(ex);
        }

    }

    private void LoadCache()
    {
        // _classificationsCacheLock.AcquireWriterLock(Timeout.Infinite); 

        Task.Factory.StartNew(() =>
        {
            try
            {
                _classificationsCacheLock.AcquireWriterLock(Timeout.Infinite); // can only set writer on or off on same thread, not between threads
                if (_classificationsCache == null)
                {
                    var cases = SomeServices.GetAllFOIRequests();
                    _classificationsCache = cases.SelectMany(c => c.Classifications.Classification.Select(cl => cl.Group)).Distinct().ToList();
                }

            }
            catch (Exception ex)
            {

                _logger.Error(ex);

            }
            finally
            {

                if (_classificationsCacheLock.IsWriterLockHeld)
                    _classificationsCacheLock.ReleaseWriterLock();
            }

        });//.ContinueWith((prevTask) =>
        //{
        //     if (_classificationsCacheLock.IsWriterLockHeld)
        //          _classificationsCacheLock.ReleaseWriterLock();
        //  });

    }

     public GetFOIRequestClassificationsList_Response GetFOIRequestClassificationsList()
    {
        try
        {

            GetFOIRequestClassificationsList_Response response = new GetFOIRequestClassificationsList_Response();

            _classificationsCacheLock.AcquireReaderLock(Timeout.Infinite);
            response.Classifications = _classificationsCache;
            _classificationsCacheLock.ReleaseReaderLock();

            return response;

        }
        catch (Exception ex)
        {
            _logger.Error(ex);

            if (ex is FaultException)
            {
                throw;
            }
            else
                throw new FaultException(ex.Message);
        }
    }
}
结果仍然是一样的。classificationsCacheLock.AcquisiteReaderLock不会像看上去那样等待/阻止

我还添加了一些诊断来检查是否

  • 该线程实际上是同一个线程,您不能期望R/W 在同一线程上阻塞
  • _classificationsCacheLock的实例完全相同 时代

    公共GetFOIRequestClassificationsList\响应GetFOIRequestClassificationsList() { 尝试 {

            GetFOIRequestClassificationsList_Response response = new GetFOIRequestClassificationsList_Response();
    
            Debug.WriteLine(string.Format("GetFOIRequestClassificationsList - _classificationsCacheLock.GetHashCode - {0}", _classificationsCacheLock.GetHashCode()));
            Debug.WriteLine(string.Format("GetFOIRequestClassificationsList - Thread.CurrentThread.ManagedThreadId - {0} ", Thread.CurrentThread.ManagedThreadId));
    
            Thread.Sleep(1000);
    
             _classificationsCacheLock.AcquireReaderLock(Timeout.Infinite);
            //_classificationsCacheMRE.WaitOne();
    
            response.Classifications = _classificationsCache;
    
            _classificationsCacheLock.ReleaseReaderLock();
    
            return response;
    
        }
        catch (Exception ex)
        {
            _logger.Error(ex);
    
            if (ex is FaultException)
            {
                throw;
            }
            else
                throw new FaultException(ex.Message);
        }
    }
    
结果是

GetFOIRequestClassificationsList - _classificationsCacheLock.GetHashCode - 16265870
GetFOIRequestClassificationsList - Thread.CurrentThread.ManagedThreadId - 9 
LoadCache - _classificationsCacheLock.GetHashCode - 16265870
LoadCache - Thread.CurrentThread.ManagedThreadId-  10  
LoadCache constructor thread - _classificationsCacheLock.GetHashCode - 22863715
LoadCache constructor thread - Thread.CurrentThread.ManagedThreadId-  9 
GetFOIRequestClassificationsList - _classificationsCacheLock.GetHashCode - 22863715
GetFOIRequestClassificationsList - Thread.CurrentThread.ManagedThreadId - 8 
LoadCache new thread - _classificationsCacheLock.GetHashCode - 22863715
LoadCache new thread - Thread.CurrentThread.ManagedThreadId-  10 
…按照这个顺序,现在我们有了一个预期的竞争条件,因为写入锁是在新创建的线程中获得的。实际的
WCF
服务调用是在构造函数的派生线程计划实际运行之前进行的。所以我移动

  _classificationsCacheLock.AcquireWriterLock(Timeout.Infinite);
因为这保证在访问任何类字段之前执行

尽管有证据表明构造函数是在与执行服务方法的
WCF
线程不同的
WCF
线程上初始化的,但AcquireWriterLock仍然没有阻塞

 private void LoadCache()
    {

        _classificationsCacheLock.AcquireWriterLock(Timeout.Infinite);

        Debug.WriteLine(string.Format("LoadCache constructor thread - _classificationsCacheLock.GetHashCode - {0}", _classificationsCacheLock.GetHashCode()));
        Debug.WriteLine(string.Format("LoadCache constructor thread - Thread.CurrentThread.ManagedThreadId-  {0} ", Thread.CurrentThread.ManagedThreadId));

        var newThread = new Thread(new ThreadStart(() =>
        {
            try
            {
                Thread.Sleep(5000);

                Debug.WriteLine(string.Format("LoadCache new thread - _classificationsCacheLock.GetHashCode - {0}", _classificationsCacheLock.GetHashCode()));
                Debug.WriteLine(string.Format("LoadCache new thread - Thread.CurrentThread.ManagedThreadId-  {0} ", Thread.CurrentThread.ManagedThreadId));


              //  _classificationsCacheLock.AcquireWriterLock(Timeout.Infinite); // can only set writer on or off on same thread, not between threads
                if (_classificationsCache == null)
                {
                    var cases = SomeServices.GetAllFOIRequests();
                    _classificationsCache = cases.SelectMany(c => c.Classifications.Classification.Select(cl => cl.Group)).Distinct().ToList();
                }

            }
            catch (Exception ex)
            {

                _logger.Error(ex);

            }
            finally
            {

                if (_classificationsCacheLock.IsWriterLockHeld)
                    _classificationsCacheLock.ReleaseWriterLock();
            }

        }));
        newThread.IsBackground = true;
        newThread.Name = "CheckQueues" + DateTime.Now.Ticks.ToString();
        newThread.Start();
}
同样,AcquireWriterLock不会阻塞并允许分配空引用classificationsCache

结果是

GetFOIRequestClassificationsList - _classificationsCacheLock.GetHashCode - 16265870
GetFOIRequestClassificationsList - Thread.CurrentThread.ManagedThreadId - 9 
LoadCache - _classificationsCacheLock.GetHashCode - 16265870
LoadCache - Thread.CurrentThread.ManagedThreadId-  10  
LoadCache constructor thread - _classificationsCacheLock.GetHashCode - 22863715
LoadCache constructor thread - Thread.CurrentThread.ManagedThreadId-  9 
GetFOIRequestClassificationsList - _classificationsCacheLock.GetHashCode - 22863715
GetFOIRequestClassificationsList - Thread.CurrentThread.ManagedThreadId - 8 
LoadCache new thread - _classificationsCacheLock.GetHashCode - 22863715
LoadCache new thread - Thread.CurrentThread.ManagedThreadId-  10 
编辑2

创建了不带源代码管理的解决方案的副本

如果你想亲自处理这个问题,请上传

更改为使用代码中的手动重置事件进行演示,并注释掉问题代码

  • 放置断点
  • 在调试中运行Example.Web
  • 在浏览器中,导航到 "及 点击按钮
MRE工作,ReaderWriterLock未按预期工作

.net 4.0-C#

CaseWork()
方法中,每次调用该方法时,您都要创建新的
ReaderWriterLock
。因此获得的锁只需发送到垃圾收集器,新的锁就会出现。因此,实际上没有正确地获得锁

为什么不使用
static
锁,在
static
构造函数中创建它呢

如果我错了,请更正我,但如果您正在更新缓存,我无法更正。如果这是真的,我建议您只需使用
Lazy
类。它是线程安全的,在设置值之前保存所有读卡器。它在内部使用
TPL
,只需使用:

private Lazy<List<string>> _classificationsCache = new Lazy<List<string>>
(() => 
{
    var cases = SomeServices.GetAllFOIRequests();
    return cases.SelectMany(c => c.Classifications.Classification.Select(cl => cl.Group)).Distinct().ToList();
});
:

如果当前线程已经具有writer锁,没有读卡器锁 已获取。相反,writer锁上的锁计数将增加。 这可以防止线程阻塞自己的写入程序锁 结果与调用
AcquireWriterLock
完全相同,并且 释放时需要额外调用
ReleaseWriterLock
写锁

我认为这件事正在发生:


您的读卡器锁获取正在同一线程内触发(
任务。StartNew
方法使用
任务调度程序。当前
属性)writer锁正在工作,因此它不会阻塞,因为它与
任务具有相同的特权,并且得到了空列表。因此,在您的情况下,您必须选择另一个同步原语。

我必须道歉,我没有重命名构造函数的原始名称,我现在已经更正了这一点。这会误导您认为我s每次都创建锁的新实例。我只为每个类实例创建一个实例,设置为single,所以WCF应该只创建一个MyService实例,为每个请求提供服务。事实上,我可以使用线程安全集合,也许这就是我在这种特殊情况下最终要做的事情。但这有多奇怪啊在这个场景中,ReaderWriterLock似乎有这样的错误行为。我还不知道我做了什么(或没有做)这可能会导致这种情况。我认为您正在启动的
任务没有正确运行ClassificationsCache不会在任务完成后填充,不会引发错误。同时后台的请求不会被classificationsCacheLock.AcquisiteReaderLock的调用阻止,而是获取空的ClassificationsnsCache,直到任务运行,然后每个后续请求都会获得填充的classificationsCache.T