gpt4 book ai didi

c# - ReaderWriterLock 在 ServiceBehavior 构造函数中不起作用

转载 作者:行者123 更新时间:2023-11-30 12:43:49 26 4
gpt4 key购买 nike

我有一个 WCF 服务,其中 InstanceContextModeSingleConcurrencyModeMultiple。目的是在实例化时创建值缓存,而不阻止不依赖缓存创建的其他服务调用。

这样只有尝试在 _classificationsCacheLock 上获取读锁的方法才需要等到 classificationsCache 的值被填充(classificationsCacheLock.IsWriterLockHeld = false )。

但问题是,尽管在任务线程中获取了一个写锁,调用 WCF 继续服务以响应对服务方法 GetFOIRequestClassificationsList() 的调用导致 _classificationsCacheLock。 IsWriterLockHeldfalse,而它应该为 true。

这是 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();

结果还是一样。 classificationsCacheLock.AcquireReaderLock 并没有像它看起来应该的那样等待/阻塞。

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

  • 线程实际上是同一个线程,你不能指望 R/W阻塞在同一个线程上
  • _classificationsCacheLock 的实例完全相同次

    公共(public) 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);

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

AcquireWriterLock 仍然不会阻塞,尽管有证据表明构造函数是在与执行服务方法的 WCF 线程不同的 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 不会阻止并允许分配空引用 classificationsCache。

结果是..

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

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

已上传 here如果您想亲自解决这个问题。

出于演示目的更改为在代码中使用手动重置事件并注释掉问题代码。

MRE 工作,ReaderWriterLock 没有按预期工作。

.net 4.0 - C#

最佳答案

<罢工>在CaseWork()您正在创建新的方法 ReaderWriterLock每次调用该方法时。因此,被获取的锁只是发送给垃圾收集器,新的锁就出现了。因此实际上并没有正确获取锁。

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

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

Update from MSDN :

If the current thread already has the writer lock, no reader lock is acquired. Instead, the lock count on the writer lock is incremented. This prevents a thread from blocking on its own writer lock. The result is exactly the same as calling AcquireWriterLock, and an additional call to ReleaseWriterLock is required when releasing the writer lock.

我认为这件事正在发生:

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

关于c# - ReaderWriterLock 在 ServiceBehavior 构造函数中不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30263172/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com