首页 > 解决方案 > 如何在不阻塞所有(或其他一些)的情况下由任何并发请求触发一次?

问题描述

我知道使用lock来确保一段代码一次只能由一个线程/请求/执行流运行。但我不太确定如何确保在不阻塞某些线程/请求的情况下只触发一次事件。以下是我尝试帮助减少被阻止的并发请求数量的方法:

static bool _isTriggered;
static bool _isTriggering;
static object _o = new object();
void _someMethod(){
    if(_isTriggered || _isTriggering) return;
    lock(_o){
       if(_isTriggered) return;
       _isTriggering = true;
       //do some long running task by triggering an event for all registered
       //handlers to do their own jobs ...
       //...                 
       _isTriggered = true;
       _isTriggering = false;
    }
}

有多个线程或请求_someMethod同时访问它。所以你可以看到可能有一些请求通过了第一次检查但被lock. 当第一次运行时lock,所有其他人(通过第一次检查但之前被阻止)将逐个运行该部分,但由于第二次if检查将立即返回。(确保事件只触发一次)。

lock如果所有请求都应该通过代码的锁定部分运行,那就太好了。但是在这里我只需要其中一个来运行该部分,所有其他都应该忽略(并且根本不应该被阻止)。

您是否有任何代码解决方案可以帮助减少被阻止请求的数量(甚至没有请求被阻止)?

更新

是否会确保只运行一次(通过检查):

ConcurrentDictionary<int, bool> _dict = ...;
if(!_dict.AddOrUpdate(someKey, false, (key, vl) => true)){
   //...
}

如果它.AddOrUpdate总是false第一次返回(作为附加值),那将符合我的要求,因为所有下一个调用都应该调用更新回调并返回true(这将绕过if检查)。

标签: c#multithreadingconcurrencycritical-section

解决方案


您是否可以创建一个变量int i = 0,然后每当线程到达临界区时,它会尝试int result = Interlocked.CompareExchange(ref i, 1, 0)?然后 if result == 0,这意味着你的线程正在初始化,否则它不是。

在这种情况下需要考虑的一些事情是

  1. 其他线程是否必须等待临界区完成初始化,或者即使临界区尚未完成它们也可以继续?这可能意味着对象未初始化并且处于某种不一致的状态。如果他们确实必须等待,也许您可​​以使用 Lazy 或 LazyInitializer 代替。
  2. 还有另一种方法来运行这个初始化吗?也许在应用程序启动时运行它会是一个更好的选择,而不是在多线程场景中懒惰地运行。

CompareExchange编辑:如果我应用该方法,您的代码可能如下所示:

static int i = 0;
void _someMethod(){
    int result = Interlocked.CompareExchange(ref i, 1, 0);
    if(result != 0) return;
    //do some long running task by triggering an event for all registered
    //handlers to do their own jobs ...
    //...                 
    }
}

只要确保如果您以后i因为任何其他原因决定访问,您总是通过Interlocked操作来完成,否则您可能会打开潘多拉魔盒。


推荐阅读