C# ReaderWriterLock在ServiceBehavior构造函数中不工作
我有一个WCF服务,其中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
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