3

I have multithreaded application with some background processing. It has long-running UI updates (on the UI-thread itself), which are invoked from the background thread via the well-known resource on MSDN. I can not shorten these UI updates since they are finally done in a external library(1).

Now, from that background thread, I want to asynchronously invoke (using BeginInvoke()) these updates on the UI thread, but only if the previous update has finished yet. If not, I would like to simply skip this update. This will prevent an overflow of the Windows Message Queue, in case the invokes come faster than the to invoked method is able to execute.

My current solution is this: In the method that executes on the UI thread, I do enter and exit a ReaderWriterLockSlim instance. On the background thread, I try to enter the instance with zero timeout. When successful, I call 'BeginInvoke()' then exit again. When not successful, I skip the method invoke altogether.

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(); }               
   }

This all works, but is there a more elegant solution? How to simply test, from the one thread, whether a method is executed on any (other) thread?

Thanks for any answers!

UPDATE: Hans Passant has helped me with his answer. See the solution below. Hopefully this helps someone else too.

/// <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();               
    }
}
Community
  • 1
  • 1
Marcel
  • 15,039
  • 20
  • 92
  • 150
  • 1
    But it is not thread-safe unless you lock access to Enqueue. You can have multiple threads going in between _idle.WaitOne and _idle.Reset. This does not seem to be an issue for your case, but worth knowing if you have multiple worker threads calling Enqueue - maybe update the code comment to reflect this. – S.Skov Dec 01 '10 at 16:53
  • @S.Skov: You are right with your remark about thread safety for Enqueue(). As you guessed right this is not an issue for my application since the worker thread is always the same. However, this should get addressed! – Marcel Dec 01 '10 at 20:53

3 Answers3

2

Since you don't want to block the background thread you could use a simple non-blocking protector:

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(/*...*/);
    }       
}

This ensures that the BeginInvoke is only called after a completion of the Update method. Any subsequent 'attempts' will not enter the if..then block

EDIT: The same if..then can of course be used in both threads, as long as the lockCookie is the same and added finally as per commenter suggestion.

S.Skov
  • 707
  • 4
  • 8
  • You think you didn't use a locking? you used it in a way that's not a good way, instead of using `lock` you implement it yourself, lock has `try`, `catch`, `finally` statements, and `Monitor` and your way doesn't have it. – Saeed Amiri Nov 26 '10 at 16:38
  • ehh what? I don't use lock anywhere. If you are thinking about lockCookie, then it is a numeric variable declared somewhere in the scope. Interlocked** ensures atomicity but does not put a given thread in a blocked mode. – S.Skov Nov 26 '10 at 16:46
  • I think you used `while`, may be I can't understand `OP`s problem, but the way you done, there are some unused call to function. – Saeed Amiri Nov 26 '10 at 17:02
  • His main thread has do some updating through a BeginInvoke call. While this is going he does not want to start another update, but he also can not block the thread that initiates the BeginInvoke. So he can not use lock or any other similar blocking synchronization option - and yes you were right, there was a finally missing in my sample. – S.Skov Nov 26 '10 at 17:06
2

This code doesn't actually do what you want it to do. It takes a while for the delegate target to start running. Your worker thread could acquire the write lock many times before that. It will only fail to acquire the lock when the Update() method happens to be busy executing.

A ManualResetEvent is what you want here. Initialize it to be set. Reset() it when you BeginInvoke(), Set() it at the end of Update(). Now you can test with WaitOne(0).

Note a corner case with this approach: your UI may well not show the last update.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Thanks Hans, for giving me the RIGHT answer, and actually understanding my intentions. I now have created a class to wrap your idea, and share it with the questions. – Marcel Nov 29 '10 at 08:44
0

My preferred approach is to define the display object in such a way that the underlying state may be updated asynchronously, so that the update command run on the UI thread doesn't need any parameters. I then have a flag which says whether an update is pending. After any change to the state, I Interlocked.Exchange the flag and, if no change was pending, I BeginInvoke the update routine. The UpdateRoutine, while the flag is set, clears the flag and does an update. If the state gets changed during an update, the update may or may not reflect the state change, but precisely one more update will happen following the last state change.

In some cases, it may be desirable to associate a timer with the update routine; initially the timer starts disabled. If an update request is received and the timer is enabled, do skip the update. Otherwise, perform the update and enable the timer (with e.g. a 50-ms interval). When the timer expires, if the update flag is set, perform another update. This approach will greatly reduce overhead if the main code attempts to e.g. update a progress bar 10,000x/second.

supercat
  • 77,689
  • 9
  • 166
  • 211