如何通过任何并发请求触发一次而不阻塞所有(或其他一些)?

How to trigger once by any concurrent request without blocking all (or some of the others)?

我知道使用 lock 来确保一段代码一次只能 运行 一个 thread/request/execution 流。但是我不太确定如何确保事件只被触发一次而不阻塞某些 threads/requests。以下是我试图帮助减少被阻止的并发请求数的方法:

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 时,所有其他人(通过第一次检查但之前被阻止)将 运行 一个接一个地通过该部分,但将 return 立即到期到第二个 if 检查。 (确保事件只触发一次)。

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

您是否有针对此问题的任何代码解决方案来帮助最大程度地减少被阻止的请求的数量(甚至没有请求被阻止)?

更新

是否能保证运行恰好一次(通过检查):

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

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

您能否创建一个变量 int i = 0,然后当线程到达临界区时,它会尝试 int result = Interlocked.CompareExchange(ref i, 1, 0)?那么如果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 操作,否则您可能会打开潘多拉问题盒。