我有一些后台处理的多线程应用程序。它有长时间运行的UI更新(在UI线程本身上),它通过众所周知的MSDN上的资源从后台线程调用。我无法缩短这些UI更新,因为它们最终是在外部库中完成的(1)。
现在,在这个后台线程中,我想异步调用(在UI线程上使用BeginInvoke()**) --这些更新,但前提是上一次更新尚未完成)。**如果没有,我想跳过这个更新。这将防止Windows消息队列溢出,以防调用速度超过to调用方法执行的速度。
我的当前解决方案是:在UI线程上执行的方法中,我确实输入和退出了一个ReaderWriterLockSlim实例。在后台线程上,我尝试输入具有零超时的实例。成功后,我调用'BeginInvoke()‘,然后再次退出。如果没有成功,我将跳过方法调用。
public void Update(double x, double y)
{
_updateLock.EnterWriteLock();
try
{ //...long running task... }
finally
{ _updateLock.ExitWriteLock(); }
}
//....
void Provider_PositionChanged(object sender, SpecialEventArgs e)
{
if (_updateLock.TryEnterWriteLock(0)) //noone is currently executing an update?
{
try { myUiControl.BeginInvoke(/*...*/); }
finally { _updateLock.ExitWriteLock(); }
}所有这些都能工作,但是有更优雅的解决方案吗?如何简单地从一个线程测试一个方法是否在任何(其他)线程上执行?
Invoke() (而不是BeginInvoke()__)不是一个选项,因为这会阻止我的后台线程,从而阻止其他东西在那里执行。谢谢你的回答!
更新:汉斯·帕桑特帮助我完成了他的回答。看到了下面的解决方案,希望这对其他人也有帮助。
/// <summary>
/// This class enqueues asynchronously executing actions (that are running on another thread), but allows
/// to execute only one action at a time. When busy, newly enqueued actions are dropped.
/// Any enqueued action is required to call Done() on this when it has finished, to allow further actions
/// to execute afterwards.
/// </summary>
/// <remarks>This class is intended to help prevent stacking UI-Updates when the CPU or other resources
/// on the machine are not able to handle the amount of updates requested. However, the user
/// must keep in mind, that using this class may result
/// in dropped updates and that the last requested update is not always executed.</remarks>
public class ActionBouncer
{
/// <summary>
/// A event that signals the idle/busy state. Idle means, that no action is currently executing.
/// </summary>
private ManualResetEvent _idle = new ManualResetEvent(true);
/// <summary>
/// Enqueues the specified action, executing it when this bouncer
/// is currently idle.
/// </summary>
/// <param name="action">The action.</param>
public void Enqueue(Action action)
{
if (_idle.WaitOne(0)) //are we idle now? (Remark: This check and the reset below is not thread-safe (thanks to s.skov))
{
_idle.Reset(); //go to busy state
action(); //execute the action now.
}//else drop the action immediately.
}
/// <summary>
/// Signal the bouncer, that the currently executing asynchronous action is done, allowing
/// subsequent requests to execute.
/// This must get explicitly called (in code) at the end of the asynchronous action.
/// </summary>
public void Done()
{
_idle.Set();
}
}发布于 2010-11-26 17:36:15
这段代码实际上并没有做你想要它做的事情。委托目标需要一段时间才能开始运行。在此之前,您的工作线程可以多次获取写入锁。只有当Update()方法正忙着执行时,它才会获得锁。
ManualResetEvent是你在这里想要的。初始化它以被设置。在BeginInvoke()时重置(),在Update()末尾设置()。现在您可以使用WaitOne(0)进行测试。
注意这种方法的一个角落:您的UI很可能不会显示上一次更新。
发布于 2010-11-26 16:29:13
由于您不想阻止后台线程,所以可以使用一个简单的非阻塞保护程序:
public void Update(double x, double y)
{
try
{
//...long running task...
}
finally
{
Interlocked.CompareExchange(ref lockCookie, 0, 1); //Reset to 0, if it is 1
}
}
//....
void Provider_PositionChanged(object sender, SpecialEventArgs e)
{
if (Interlocked.CompareExchange(ref lockCookie, 1, 0) == 0) //Set to 1, if it is 0
{
myUiControl.BeginInvoke(/*...*/);
}
}这确保BeginInvoke只在Update方法完成后才被调用。任何后续的“尝试”都不会进入if..then块
编辑:当然,在两个线程中都可以使用相同的if..then,只要lockCookie是相同的,最后按照评论建议添加。
发布于 2010-12-23 18:37:25
我喜欢的方法是定义显示对象,使底层状态可以异步更新,这样在UI线程上运行的update命令就不需要任何参数。然后,我有一个标志,表示是否正在等待更新。在对状态进行任何更改后,我将Interlocked.Exchange标志,如果没有更改,则BeginInvoke更新例程。设置标志时,UpdateRoutine清除标志并执行更新。如果状态在更新期间发生更改,则更新可能反映或不反映状态更改,但在最后一次状态更改之后将发生另一次更新。
在某些情况下,可能希望将计时器与更新例程相关联;最初,计时器启动时禁用。如果收到更新请求并启用了计时器,请跳过更新。否则,执行更新并启用计时器(例如,50 ms间隔)。当计时器过期时,如果设置了更新标志,则执行另一个更新。如果主代码试图更新进度条10,000 x/秒,这种方法将大大减少开销。
https://stackoverflow.com/questions/4286708
复制相似问题