While 循环计时如何在带有多个线程的 C# 中工作?

Posted

技术标签:

【中文标题】While 循环计时如何在带有多个线程的 C# 中工作?【英文标题】:How does While Loop timing work in C# w/ multiple threads? 【发布时间】:2021-10-10 17:13:28 【问题描述】:

我有一个可以从多个线程更新的 isRunning 布尔变量。只要 isRunning 为真,我就有一个 while 循环运行。在循环开始时,它立即再次检查布尔值是否为真。从概念上讲,我可以看到 while 循环将 isRunning 视为 true 的情况,然后另一个线程在 if 语句被处理之前更新该值。在实践中,我想象 isRunning 的时间跨度将有亚毫秒的变化,以便永远命中 if 语句。对我来说,这感觉就像是臭代码。

while(isRunning) 
       //isRunning gets updated by another thread??
   if(!isRunning) 
      //Will this ever get hit???
   
 

if 块可以被触发吗?在执行 while 检查之后紧跟 if 检查之间有什么样的时间跨度?我将如何进行实验?

【问题讨论】:

"if 块是否会被触发?" - 绝对地。 while 条件仅在循环开始时检查。如果另一个线程可以随时更新布尔值,那么循环以 isRunning 为 true 并且 isRunning 在 if 条件之前变为 false 肯定有可能。 您可能希望使用 ManualResetEvent 来达到您的目的 您的“实践中”假设非常危险。 "在执行 while 检查和紧跟 if 检查之间有什么样的时间跨度?"您遗漏了很多代码,尤其是 while 循环的主体和在其他线程中运行的代码。所以很难说。然而,所有需要发生的只是一个简单的上下文切换,这最终是你无法控制的。简而言之 - 有可能 优化可能意味着isRunning 存储在CPU 寄存器中,因此将永远在另一个线程上显示为false(无限循环)。这段代码完全不是线程安全的,这种事情你需要使用Interlocked 【参考方案1】:

可以提出各种理论论证,但让我们尝试一下。我不会很小心,因为那不是重点。关键是要区分击中if 块是否是天文罕见的事件。

例如:

static void Main()

    int IfBlockHitCount = 0;
    for (int i = 0; i < 1000; i++)
    
        Thread runner = new Thread(RandomlyResetRunningFlag);
        IsRunning = true;
        runner.Start();
        while (IsRunning)
        
            if (!IsRunning)
                IfBlockHitCount++;
        
    

    Console.WriteLine(IfBlockHitCount);


static volatile bool IsRunning;
static volatile int sink;

static void RandomlyResetRunningFlag()

    Random r = new Random();
    int spinCount = r.Next(1000, 1000000);
    for (int i = 0; i < spinCount; i++)
        sink = 0;
    IsRunning = false;

我运行了几次,打印的数字非常多,通常不到 500(即不到一半的时间)。由此我得出结论,是的,很可能会击中 if

但是为什么呢?我提出这个解释。由于执行忙等待的线程基本上做了两件事:

    while 的条件下检查IsRunning。 在if 的条件下检查IsRunning

真的没有其他地方可以花时间。考虑到IsRunning 首次被观察为false 的随机时间,在这两种情况下都存在相当大的机会。如果线程实际上在做任何事情,那将大大改变结果。它以何种方式改变它,取决于 在哪里线程正在做某事:

while (IsRunning)

    // A
    if (!IsRunning)
        IfBlockHitCount++;
    // B

如果在位置 A 添加更多代码,则更有可能执行 if 块。如果在位置 B 添加更多代码,则执行 if 块的可能性会降低。

换一种说法:这并不是时间窗口有多短的问题,而是时间窗口相对于该窗口之外的时间量有多大的问题。如果没有其他事情发生,即使是一个很短的窗口也可以覆盖整个时间线的大部分时间。

顺便说一句,IsRunning 必须是volatile,否则整个问题都没有实际意义。 (提醒专家,C# volatile 与 C++ volatile 相同,实际上适用于这种用法,尽管不一定推荐)我还做了一个 volatile @ 987654337@,为了强制那个随机持续时间的等待循环实际做某事,如果循环被优化掉会很糟糕。

【讨论】:

以上是关于While 循环计时如何在带有多个线程的 C# 中工作?的主要内容,如果未能解决你的问题,请参考以下文章

使用 sleep() 函数作为带有 while 循环的计时器不起作用?

C# Windows 服务 - 我的计时器不工作,获得多个线程

C# 计时器是不是在单独的线程上运行?

立即安全地终止线程 (C#)

如何等待多个 C# 事件?

具有多个条件的C#中的while循环