我有 WCF 服务 where InstanceContextMode
isSingle
和ConcurrencyMode
is Multiple
。目的是在实例化时创建值的缓存,而不会阻止其他不依赖于缓存创建的服务调用。
这样,只有尝试获得读取锁定的方法才_classificationsCacheLock
需要等待,直到classificationsCache
填充 ( classificationsCacheLock.IsWriterLockHeld = false
) 的值。
然而问题是,尽管在任务线程中获得了写锁,调用 WCF 继续服务以响应对服务方法的调用,GetFOIRequestClassificationsList()
结果_classificationsCacheLock.IsWriterLockHeld
是false
,当它应该为真时。
这是WCF
实例化的奇怪行为还是我从根本上错过了一个技巧。
我尝试在构造函数的线程上下文(安全选项)和生成的任务线程的上下文中获取写锁(这可能会在调用函数调用比调用调用更快)之间引入竞争,WCF
但GetFOIRequestClassificationsList()
两者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
创建了没有源代码控制的解决方案副本。
如果您想解决这个问题,请在此处上传。
更改为在代码中使用手动重置事件进行演示,并注释掉问题代码。
- 放置断点
- 在调试中运行 Example.Web
- 在浏览器中导航到“ http://localhost:11164/GetFOIRequestClassificationsList.htm ”并单击按钮。
MRE 有效,ReaderWriterLock 无法按预期工作。
.net 4.0 - C#