C# 如何避免在不阻塞的情况下并发调用方法?
我想运行一个可能运行几秒钟的清理任务。多个线程可以调用此任务,但我只想运行此任务一次。所有其他调用都应该跳过C# 如何避免在不阻塞的情况下并发调用方法?,c#,multithreading,synchronization,locking,nonblocking,C#,Multithreading,Synchronization,Locking,Nonblocking,我想运行一个可能运行几秒钟的清理任务。多个线程可以调用此任务,但我只想运行此任务一次。所有其他调用都应该跳过 下面是我当前的实现,但我无法想象在.net框架中没有更好的解决方案,导致代码行数减少 object taskLock; bool isRunning; void Task() { if (isRunning) return; try { lock (taskLock)
下面是我当前的实现,但我无法想象在.net框架中没有更好的解决方案,导致代码行数减少
object taskLock;
bool isRunning;
void Task()
{
if (isRunning) return;
try
{
lock (taskLock)
{
if (isRunning) return;
isRunning = true;
}
// Perform the magic
}
finally
{
isRunning = false;
}
}
是的,有更好的解决办法。您可以使用,代码变得更简单且无锁:
class Worker
{
private volatile int isRunning = 0;
public void DoWork()
{
if (isRunning == 0 && Interlocked.CompareExchange(ref isRunning, 1, 0) == 0)
{
try
{
DoTheMagic();
}
finally
{
isRunning = 0;
}
}
}
private void DoTheMagic()
{
// do something interesting
}
}
在这种情况下,互锁。CompareExchange
作为原子操作(伪代码)执行以下操作:
从MSDN文档中:
public static int CompareExchange(
ref int location1,
int value,
int comparand
)
如果comparand和位置1中的值相等,则值为
存储在位置1。否则,不执行任何操作。比较
交换操作作为原子操作执行。这个
CompareExchange的返回值是位置1中的原始值,
无论交换是否发生
您可以使用类似的方法,但使用并发字典代替taskLock对象。tbh-如果它目前有效,继续并分解积压工作中的其他项目“我无法想象没有更好的解决方案”这是一个非正统的问题,因为根据定义,它充满了并发歧义。根据时间的不同,任务可能只运行一次,或者对每个调用线程运行一次。至少,它是低效的。是的,它可以是非常低效的,但我看不到一个更好的替代信号,为某项任务的发生。另一种方法是使用重置事件,任务只等待另一个线程设置事件,但我必须管理一个线程/任务。我喜欢这个解决方案,但CompareExchange不是比
if(isRunning)返回慢吗代码>?如果(isRunning==1 | | Interlocked.compareeexchange(ref isRunning,1,0)==1)返回,则可能执行if(isRunning==1 | | Interlocked.compareeexchange当任务更经常地运行时,代码>可能会更快not@RamonSmits是的,它会更快,但是如果(isRunning==0&&Interlocked),它应该是。比较交换(ref isRunning,1,0)==0)
,您需要将isRunning
声明为volatile。另一方面,互锁。CompareExchange
非常快。“我不相信你会得到可观的收益。”拉蒙斯密茨说。我编辑了我的答案以包含优化。这里有一个微基准测试,比较优化为什么需要“volatile”关键字?@RamonSmits,因为它可以被其他线程修改。它可以防止编译器执行一些可能导致错误结果的优化。它与记忆栅栏有关
public static int CompareExchange(
ref int location1,
int value,
int comparand
)