首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >WinForms多线程:只有在上一次更新完成后才执行GUI更新

WinForms多线程:只有在上一次更新完成后才执行GUI更新
EN

Stack Overflow用户
提问于 2010-11-26 15:52:39
回答 3查看 962关注 0票数 3

我有一些后台处理的多线程应用程序。它有长时间运行的UI更新(在UI线程本身上),它通过众所周知的MSDN上的资源从后台线程调用。我无法缩短这些UI更新,因为它们最终是在外部库中完成的(1)。

现在,在这个后台线程中,我想异步调用(在UI线程上使用BeginInvoke()**) --这些更新,但前提是上一次更新尚未完成)。**如果没有,我想跳过这个更新。这将防止Windows消息队列溢出,以防调用速度超过to调用方法执行的速度。

我的当前解决方案是:在UI线程上执行的方法中,我确实输入和退出了一个ReaderWriterLockSlim实例。在后台线程上,我尝试输入具有零超时的实例。成功后,我调用'BeginInvoke()‘,然后再次退出。如果没有成功,我将跳过方法调用。

代码语言:javascript
复制
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(); }               
   }

所有这些都能工作,但是有更优雅的解决方案吗?如何简单地从一个线程测试一个方法是否在任何(其他)线程上执行?

谢谢你的回答!

更新:汉斯·帕桑特帮助我完成了他的回答。看到了下面的解决方案,希望这对其他人也有帮助。

代码语言:javascript
复制
/// <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();               
    }
}
EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2010-11-26 17:36:15

这段代码实际上并没有做你想要它做的事情。委托目标需要一段时间才能开始运行。在此之前,您的工作线程可以多次获取写入锁。只有当Update()方法正忙着执行时,它才会获得锁。

ManualResetEvent是你在这里想要的。初始化它以被设置。在BeginInvoke()时重置(),在Update()末尾设置()。现在您可以使用WaitOne(0)进行测试。

注意这种方法的一个角落:您的UI很可能不会显示上一次更新。

票数 2
EN

Stack Overflow用户

发布于 2010-11-26 16:29:13

由于您不想阻止后台线程,所以可以使用一个简单的非阻塞保护程序:

代码语言:javascript
复制
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是相同的,最后按照评论建议添加。

票数 2
EN

Stack Overflow用户

发布于 2010-12-23 18:37:25

我喜欢的方法是定义显示对象,使底层状态可以异步更新,这样在UI线程上运行的update命令就不需要任何参数。然后,我有一个标志,表示是否正在等待更新。在对状态进行任何更改后,我将Interlocked.Exchange标志,如果没有更改,则BeginInvoke更新例程。设置标志时,UpdateRoutine清除标志并执行更新。如果状态在更新期间发生更改,则更新可能反映或不反映状态更改,但在最后一次状态更改之后将发生另一次更新。

在某些情况下,可能希望将计时器与更新例程相关联;最初,计时器启动时禁用。如果收到更新请求并启用了计时器,请跳过更新。否则,执行更新并启用计时器(例如,50 ms间隔)。当计时器过期时,如果设置了更新标志,则执行另一个更新。如果主代码试图更新进度条10,000 x/秒,这种方法将大大减少开销。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/4286708

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档