gpt4 book ai didi

c# - Monitor.TryEnter 和 Threading.Timer 竞争条件

转载 作者:太空狗 更新时间:2023-10-29 22:00:10 25 4
gpt4 key购买 nike

我有一个 Windows 服务,每 5 秒检查一次工作。它使用 System.Threading.Timer 来处理检查和处理,并使用 Monitor.TryEnter 来确保只有一个线程正在检查工作。

假设它必须是这种方式,因为以下代码是服务创建的其他 8 个 worker 的一部分,每个 worker 都有自己需要检查的特定工作类型。

readonly object _workCheckLocker = new object();

public Timer PollingTimer { get; private set; }

void InitializeTimer()
{
if (PollingTimer == null)
PollingTimer = new Timer(PollingTimerCallback, null, 0, 5000);
else
PollingTimer.Change(0, 5000);

Details.TimerIsRunning = true;
}

void PollingTimerCallback(object state)
{
if (!Details.StillGettingWork)
{
if (Monitor.TryEnter(_workCheckLocker, 500))
{
try
{
CheckForWork();
}
catch (Exception ex)
{
Log.Error(EnvironmentName + " -- CheckForWork failed. " + ex);
}
finally
{
Monitor.Exit(_workCheckLocker);
Details.StillGettingWork = false;
}
}
}
else
{
Log.Standard("Continuing to get work.");
}
}

void CheckForWork()
{
Details.StillGettingWork = true;
//Hit web server to grab work.
//Log Processing
//Process Work
}

问题来了:
上面的代码允许 2 个定时器线程进入 CheckForWork() 方法。老实说,我不明白这是怎么可能的,但我在运行该软件的多个客户端上都遇到过这种情况。

我今天推送一些工作时得到的日志显示它检查了两次工作,我有 2 个线程独立尝试处理,这一直导致工作失败。

Processing 0-3978DF84-EB3E-47F4-8E78-E41E3BD0880E.xml for Update Request. - at 09/14 10:15:501255801
Stopping environments for Update request - at 09/14 10:15:501255801
Processing 0-3978DF84-EB3E-47F4-8E78-E41E3BD0880E.xml for Update Request. - at 09/14 10:15:501255801
Unloaded AppDomain - at 09/14 10:15:10:15:501255801
Stopping environments for Update request - at 09/14 10:15:501255801
AppDomain is already unloaded - at 09/14 10:15:501255801
=== Starting Update Process === - at 09/14 10:15:513756009
Downloading File X - at 09/14 10:15:525631183
Downloading File Y - at 09/14 10:15:525631183
=== Starting Update Process === - at 09/14 10:15:525787359
Downloading File X - at 09/14 10:15:525787359
Downloading File Y - at 09/14 10:15:525787359

日志是异步写入并排队的,所以不要深入挖掘时间完全匹配的事实,我只是想指出我在日志中看到的显示我有 2 个线程的内容点击一段我认为永远不应该被允许的代码。 (虽然日志和时间是真实的,只是经过清理的消息)

最终发生的事情是 2 个线程开始下载一个足够大的文件,其中一个线程最终拒绝访问该文件并导致整个更新失败。

上面的代码怎么能允许这样做呢?去年我遇到了这个问题,当时我有一个 lock 而不是 Monitor 并假设这只是因为计时器最终开始获得足够的偏移量由于 lock 阻止了我正在堆叠计时器线程,即一个阻塞了 5 秒并在 Timer 触发另一个回调时通过了正确的时间,并且它们都以某种方式进入了它。这就是我使用 Monitor.TryEnter 的原因 选项,这样我就不会一直堆叠计时器线程。

有什么线索吗?在我之前尝试解决此问题的所有情况下,System.Threading.Timer 一直是一个常数,我认为这是根本原因,但我不明白为什么。

最佳答案

我可以在日志中看到您提供了一个 AppDomain 重新启动,对吗?如果是,您确定在 AppDomain 重新启动期间您有一个唯一的服务对象吗?我认为在此期间并非所有线程都同时停止,其中一些线程可以继续轮询工作队列,因此不同 AppDomain 中的两个不同线程得到相同的 Id 工作。

您可能可以通过使用 static 关键字标记您的 _workCheckLocker 来解决此问题,如下所示:

static object _workCheckLocker;

并通过初始化该字段为您的类引入静态构造函数(如果是内联初始化,您可能会遇到一些更复杂的问题),但我不确定这是否足以满足您的情况 - 在 期间AppDomain 重启静态类也会重新加载。据我了解,这不适合您。

也许您可以为您的工作人员引入 static 字典而不是对象,这样您就可以检查正在处理的文档的 Id

另一种方法是为您的服务处理Stopping 事件,这可能会在AppDomain 重启期间调用,您将在其中引入CancellationToken。 ,并在这种情况下使用它来停止所有工作。

此外,正如@fernando.reyes 所说,您可以为同步引入称为互斥锁的重锁结构,但这会降低您的性能。

关于c# - Monitor.TryEnter 和 Threading.Timer 竞争条件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39515192/

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