Brannon
Brannon

Reputation: 5424

such a thing as Control.Invoke(timeout) in WinForms?

I have some gauges in my application. Sometimes, when the UI is busy, my gauge thread stalls waiting to update some gauges. In that situation, I would like to simply abandon my plan and try updating the gauges on the next poll. I currently use the Control.Invoke to move from my data polling thread into the UI. I don't want to use BeginInvoke because I don't want to waste precious UI time updating gauges when it's only the final value that matters. Is there some other way to invoke code on the UI thread in a way that I can bail out early if I can't get on the UI thread in 40ms? Is the code in the Invoke method necessary, or is there some other way to invoke a method on the main thread?

Upvotes: 2

Views: 1449

Answers (4)

JaredPar
JaredPar

Reputation: 755357

There is no inherent support for adding a timeout to Control.Invoke. However you could simulate this via BeginInvoke and a time out check.

static void Invoke(this Control control, TimeSpan timeout, MethodInvoker callback)
{
    if (!control.InvokeRequired) {
        callback();
        return;
    }

    using (ManualResetEvent mre = new ManualResetEvent(initialState: false)) {
        bool cancelled = false;
        MethodInvoker wrapped = () => {
            mre.Set();
            if (!cancelled) {
                callback();
            }
        };

        control.BeginInvoke(wrapped);
        if (!mre.WaitOne(timeout)) {
            cancelled = true;
        }
    }
}

This method will fairly accurately simulate Control.Invoke with a timeout.

Upvotes: 2

Reed Copsey
Reed Copsey

Reputation: 564811

There is no timeout option available. One option would be to use BeginInvoke, but only if the previous message has been processed. This will require thread synchronization, but could be written similarly to:

// using
object syncObj = new object();
bool operationPending = false;

while (operation)
{
   // ... Do work

   // Update, but only if there isn't a pending update
   lock(syncObj)
   {
       if (!operationPending)
       {
           operationPending = true;
           control.BeginInvoke(new Action( () =>
           {
               // Update gauges

               lock(syncObj)
                  operationPending = false;
           }));
       }
   }
}

// Update at the end (since you're last update might have been skipped)
control.Invoke(new Action(UpdateGuagesCompleted));

While this wouldn't timeout, it would prevent you from "flooding" UI events onto the main thread, as only one operation would be processed at a time.


Edit: As Yaur mentioned, this approach can also be done without locking via Interlocked:

while (operation)
{
    int pendingOperations = 0;
    // ... Do work

    // Update, but only if there isn't a pending update
    if (0 == Interlocked.CompareExchange(ref pendingOperations, 1, 0))
    {
        control.BeginInvoke(new Action( () =>
        {   
            // Update gauges

            // Restore, so the next UI update can occur
            Interlocked.Decrement(ref pendingOperations);
        }));
    }       
}

// Update at the end (since you're last update might have been skipped)
control.Invoke(new Action(UpdateGuagesCompleted));

Upvotes: 3

Luc Morin
Luc Morin

Reputation: 5380

How about storing only the latest value to a variable, and then handle the Application.Idle event ?

The way I understand it, Application.Idle is fired when the application's message pump on the UI thread goes idle, thus making sure the UI is ready to update your gauge(s)

Upvotes: 0

Servy
Servy

Reputation: 203825

If you think you might end up with a lot of updates to make over a very short span of time, in which each update would overwrite the others, the better solution is more likely to just create a timer that fires every so often and updates the UI based on whatever your worker has determined it should do. Create an instance field that represents the data used to update the UI, have the worker set it whenever it wants to update it, and then have the timer update the UI using that stored data whenever it ticks. If the worker happened to change the variable 10 times between tick events, the UI is none the wiser.

Upvotes: 0

Related Questions