2011-09-27 31 views
5

下面是一个例子:如何处理多线程中的竞态条件?

if (control.InvokeRequired) 
{ 
    control.BeginInvoke(action, control); 
} 
else 
{ 
    action(control); 
} 

如果条件和之间的BeginInvoke什么叫控制设置,例如?

具有事件做又如:

var handler = MyEvent; 

if (handler != null) 
{ 
    handler.BeginInvoke(null, EventArgs.Empty, null, null); 
} 

如果MyEvent是第一行,if语句,if语句仍然会执行之间退订。但是,这是适当的设计?如果取消订阅也会导致正确调用该事件所需的状态被破坏?这个解决方案不仅有更多的代码行(样板文件),而且它不那么直观,并且可能导致客户端的意外结果。

什么说你呢?

+0

上面描述的用于事件处理程序的模式存在的原因是保持对处理程序的引用处于活动状态,因此无法处理。 –

+0

@Mitch Wheat:是的,我并不是说处理程序必须处置,但是如果客户端退出事件,他或她也可能决定不再需要某种通常只由事件处理程序使用的状态对象。由于该事件仍然在退订后出现不幸的时机,因此可能很难追踪错误,因为预期的结果是处理程序在退订后不会运行。 –

+0

@Mitch看到我的答案。 –

回答

5

在我看来,如果这是一个问题,你的线程管理和对象生命周期管理都是鲁莽的,需要重新检查。

在第一个示例中,代码不是对称的:BeginInvoke不会等待action完成,但直接调用将会;这可能已经是一个bug了。

如果您希望另一个线程可能会处理您正在使用的控件,您别无选择,只能赶上ObjectDisposedException - 并且可能不会抛出,直到您已经在action之内,并且可能不会在当前线程上感谢BeginInvoke

假设一旦您取消订阅某个活动,您将不再接收通知,这是不正确的。它甚至不需要多线程发生这种情况 - 只有多个订户。如果第一个用户在处理导致第二个用户取消订阅的通知时做了某些操作,那么当前“正在进行中”的通知仍然会转到第二个用户。您可以通过在事件处理程序例程顶部的警卫子句来缓解此问题,但无法阻止它发生。

+0

我会仔细查看允许一个订阅者导致一个事件,使第二个订阅者退出同一个事件的代码 –

+0

+1:消息队列保证传送,而不是未送达! – SingleNegationElimination

3

有解决竞争条件的几个技巧:

  • 裹有互斥整个事情。确保每个线程都必须首先获得一个锁,才能在比赛中开始跑步。这样,只要你获得锁定,就知道没有其他线程正在使用该资源,并且可以安全地完成。
  • 找到一种方法来检测并从中恢复;这可能非常棘手,但某些类型的应用程序运行良好;处理这个问题的一个典型方法是记录资源发生变化的次数;如果您完成一项任务并发现版本号与开始时的版本号不同,请阅读新版本并从头开始执行任务(或仅仅是失败)
  • 重新设计应用程序以仅使用原子操作;基本上这意味着使用可以一步完成的操作;这通常涉及“比较和交换”操作,或者将所有事务的数据放入可以原子写入的单个磁盘块中。
  • 重新设计应用程序以使用无锁技术;这个选项只有在满足硬实时约束比服务每个请求更重要时才有意义,因为无锁设计本质上会丢失数据(通常具有一些低优先级性质)。

哪个选项是“正确”取决于应用程序。每个选项都有性能权衡,可能会使并发性的好处不太吸引人。

-1

如果这种行为正在蔓延在你的应用程序的多个地方,它可能值得重新设计的API,它看起来像:

if(!control.invokeIfRequired()){ 
    action(action); 
} 

只是同样的想法为标准JDK库ConcurrentHashMap.putIfAbsent(...)。当然,你需要在这个新的control.invokeIfRequired()方法中处理同步。

+0

谢谢,但它已经是扩展方法的一部分:) –

+0

第二个例子非常有趣,因为'handler'可能被其他线程指定为null。我们在这里需要一个锁对象,所以每个读写操作都由同一个锁保护。我不确定C#。但在Java中,工作代码可能如下所示:'synchronized(lockObj){if(handler!= null){handler.beginInvoke(...)}}' –

+1

-1此建议虽然有用,但它不适用于这个问题。 –