Reputation: 5424
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
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
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
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
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