Polling the right way?
Asked Answered
R

4

28

I am a software/hardware engineer with quite some experience in C and embedded technologies. Currently i am busy with writing some applications in C# (.NET) that is using hardware for data acquisition. Now the following, for me burning, question:

For example: I have a machine that has an endswitch for detecting the final position of an axis. Now i am using a USB Data acquisition module to read the data. Currently I am using a Thread to continuously read the port-status.

There is no interrupt functionality on this device.

My question: Is this the right way? Should i use timers, threads or Tasks? I know polling is something that most of you guys "hate", but any suggestion is welcome!

Robbynrobe answered 28/4, 2014 at 12:15 Comment(6)
Why would we hate polling?Saprolite
Is there an event like .OnFinalPositionArrived you could hook up to?Diploid
You should use a System.Threading.Timer to do the polling - that avoids creating a new thread to do so.Dominoes
@L-Three: Because when i'm searching for the best way to implement this, i see a lot of disapproving comments about polling. That's why i made that comment ;)Robbynrobe
@Einer: The library supplied with the USB DAQ module sadly doesn't support events like that :(Robbynrobe
How about using conditional signalling to poll in Pthreads?Tanyatanzania
C
61

IMO, this heavily depends on your exact environment, but first off - You should not use Threads anymore in most cases. Tasks are the more convenient and more powerful solution for that.

  • Low polling frequency: Timer + polling in the Tick event:
    A timer is easy to handle and stop. No need to worry about threads/tasks running in the background, but the handling happens in the main thread

  • Medium polling frequency: Task + await Task.Delay(delay):
    await Task.Delay(delay) does not block a thread-pool thread, but because of the context switching the minimum delay is ~15ms

  • High polling frequency: Task + Thread.Sleep(delay)
    usable at 1ms delays - we actually do this to poll our USB measurement device

This could be implemented as follows:

int delay = 1;
var cancellationTokenSource = new CancellationTokenSource();
var token = cancellationTokenSource.Token;
var listener = Task.Factory.StartNew(() =>
{
    while (true)
    {
        // poll hardware

        Thread.Sleep(delay);
        if (token.IsCancellationRequested)
            break;
    }

    // cleanup, e.g. close connection
}, token, TaskCreationOptions.LongRunning, TaskScheduler.Default);

In most cases you can just use Task.Run(() => DoWork(), token), but there is no overload to supply the TaskCreationOptions.LongRunning option which tells the task-scheduler to not use a normal thread-pool thread.
But as you see Tasks are easier to handle (and awaitable, but does not apply here). Especially the "stopping" is just calling cancellationTokenSource.Cancel() in this implementation from anywhere in the code.

You can even share this token in multiple actions and stop them at once. Also, not yet started tasks are not started when the token is cancelled.

You can also attach another action to a task to run after one task:

listener.ContinueWith(t => ShutDown(t));

This is then executed after the listener completes and you can do cleanup (t.Exception contains the exception of the tasks action if it was not successful).

Coonskin answered 28/4, 2014 at 12:21 Comment(6)
The environment is a tablet with Windows 8. The application is WPF. The interval needs to be high, so you would suggest to use a Task with the Thread.Sleep(delay)? Why are Threads not the right solution? Thanks for the info!Robbynrobe
Great! Thank you for your very clear example and explanation! I didn't know that the context switching of the Task.Delay was approximately 15ms! Still the little question: Why are Threads directly, not a good solution in this case? Because of the thread-pool?Robbynrobe
See my extended explanations, but Threads are not bad per se, but the recommendation is to not use them anymore (in most cases) as the overhead for Tasks are minimal, but the "usability benefits" are big...Coonskin
Okay! But from a performance-technically point of view: On a multicore environment, will Tasks perform better than (the lower) Threads? Or are they both sliced up in pieces for the cores?Robbynrobe
They perform the same as soon as they are running, as a Task is simply said a "wrapped Thread". A task just has some overhead during start and stop to do "all the magic"...Coonskin
Thank you for the extended explanation! I will replace the current Thread solution with the Task solution!Robbynrobe
B
3

IMO polling cannot be avoided.

What you can do is create a module, with its independent thread/Task that will poll the port regularly. Based on the change in data, this module will raise the event which will be handled by the consuming applications

Beene answered 28/4, 2014 at 12:22 Comment(3)
This is also a good idea! But the module itself consists out of a thread or Task right? And after the change you fire an event?Robbynrobe
Sorry, you already suggested that! I've overlooked your answer! ;)Robbynrobe
I personally would like to use Tasks (blog.slaks.net/2013-10-11/threads-vs-tasks), but generally it depends on the urgency of work and the level of confidence with the libraryBeene
E
1

May be:

   public async Task Poll(Func<bool> condition, TimeSpan timeout, string message = null)
    {
        // https://github.com/dotnet/corefx/blob/3b24c535852d19274362ad3dbc75e932b7d41766/src/Common/src/CoreLib/System/Threading/ReaderWriterLockSlim.cs#L233 
        var timeoutTracker = new TimeoutTracker(timeout);
        while (!condition())
        {
            await Task.Yield();
            if (timeoutTracker.IsExpired)
            {
                if (message != null) throw new TimeoutException(message);
                else throw new TimeoutException();
            }
        }
    }

Look into SpinWait or into Task.Delay internals either.

Empathize answered 1/11, 2018 at 7:40 Comment(0)
G
0

I've been thinking about this and what you could probably do is build an abstraction layer on utilizing Tasks and Func, Action with the Polling service taking in the Func, Action and polling interval as args. This would keep the implementation of either functionality separate while having them open to injection into the polling service.

So for example you'd have something like this serve as your polling class

public class PollingService {
    public void Poll(Func<bool> func, int interval, string exceptionMessage) {
        while(func.Invoke()){
            Task.Delay(interval)
        }
        throw new PollingException(exceptionMessage)
    }

    public void Poll(Func<bool, T> func, T arg, int interval, string exceptionMessage) 
    {
        while(func.Invoke(arg)){
            Task.Delay(interval)
        }
        throw new PollingException(exceptionMessage)
    }
}
Galliot answered 30/7, 2022 at 1:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.