4

我有 WCF 服务 where InstanceContextModeisSingleConcurrencyModeis Multiple。目的是在实例化时创建值的缓存,而不会阻止其他不依赖于缓存创建的服务调用。

这样,只有尝试获得读取锁定的方法才_classificationsCacheLock需要等待,直到classificationsCache填充 ( classificationsCacheLock.IsWriterLockHeld = false) 的值。

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

这是WCF实例化的奇怪行为还是我从根本上错过了一个技巧。

我尝试在构造函数的线程上下文(安全选项)和生成的任务线程的上下文中获取写锁(这可能会在调用函数调用比调用调用更快)之间引入竞争,WCFGetFOIRequestClassificationsList()两者classificationsCacheLock.AcquireWriterLock(Timeout.Infinite);都导致classificationsCacheLock.IsWriterLockHeld尽管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);
        }
    }
}

编辑 1

由于许多建议是围绕线程池中的不确定性以及任务如何处理线程亲和性,我更改了方法以显式生成一个新线程

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

                Debug.WriteLine(string.Format("LoadCache - _classificationsCacheLock.GetHashCode - {0}", _classificationsCacheLock.GetHashCode()));
                Debug.WriteLine(string.Format("LoadCache - 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 = "MyNewThread" 
        newThread.Start();

结果还是一样。分类CacheLock.AcquireReaderLock 不会像它看起来应该的那样等待/阻塞。

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

  • 该线程实际上是同一个线程,您不能期望 R/W 阻塞同一个线程
  • _classificationsCacheLock 的实例始终相同

    公共 GetFOIRequestClassificationsList_Response 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  

..按照这个顺序,现在我们有一个预期的竞争条件,因为在新创建的线程中获得了写锁。实际的WCF服务调用是在构造函数的派生线程被安排实际运行之前进行的。所以我搬家

  _classificationsCacheLock.AcquireWriterLock(Timeout.Infinite);

到构造函数,因为这保证在访问任何类字段之前执行。

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

 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 不会阻塞并允许分配空引用分类缓存。

结果是..

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

创建了没有源代码控制的解决方案副本。

如果您想解决这个问题,请在此处上传。

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

MRE 有效,ReaderWriterLock 无法按预期工作。

.net 4.0 - C#

4

1 回答 1

1

在每次调用该CaseWork()方法时,您都在创建新的方法。ReaderWriterLock所以被获取的锁只是简单地发送给垃圾收集器,新的锁就会出现。因此,实际上没有正确获取任何锁。

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

如果我错了,请纠正我,但如果您要更新缓存,我不能。如果这是真的,我建议你简单地使用这个Lazy<T>类。它是线程安全的,并在设置值之前保存所有读取器。它在内部使用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();
});

你可以得到这样的值:

response.Classifications = _classificationsCache.Value;

从 MSDN 更新

如果当前线程已经拥有写锁,则不获取读锁。相反,写入锁上的锁计数会增加。这可以防止线程阻塞自己的写入器锁。结果和调用完全一样,释放写锁时需要AcquireWriterLock额外调用ReleaseWriterLock

我认为这件事正在发生:

您的读取器锁获取正在同一线程内触发(Task.StartNew方法使用该TaskScheduler.Current属性),写入器锁正在工作,因此它不会阻塞,因为它具有与Taskdo 相同的权限,并且获得了空列表。因此,在您的情况下,您必须选择另一个同步原语。

于 2015-05-15T17:56:28.193 回答