❔ Executing a method every n seconds but only during certain conditions
What would be the best way to do this? The easiest way I can think of would be:
But this feels like it might be wasteful, especially if I end up having 100s of these tasks running. Is there a better way to do this?
57 Replies
i think thats the best u can do, its simple and has next to no overhead.
the question is more about if thats really what u want to do.
lets assume for a moment that the condition is always met:
so if u want it to start doing stuff every second, then this approach is wrong, because the time to do stuff is not accounted for.
basically if doing stuff would take 500ms u would still wait 1s afterwards, if its desired to be kicked off every 1s then u would need to measure the execution time of doing stuff and substract that from 1s. or simplier: use a
PeriodicTimer
(https://learn.microsoft.com/en-us/dotnet/api/system.threading.periodictimer?view=net-7.0)
if u want to indeed have 1sec delay between executions, the code example is fine.
another thing is what u do with that condition, this looks a bit like u write a polling method instead of using proper event constructs, but to suggest something on this matter u would have to explain more about the real use caseI'm using it for a file explorer app, I would use the build in file change listener thing to listen to when the current folder changes in some way, and that would update the app
But if the folder changes extremely quickly, it could freeze the app, so I just want updates to happen every 500 ms~ maybe, and just calculate the folder changes in that tick
well not every 500 ms, but no less than 500ms
have you actually checked that the filechangelistener is not good enough?
hmm i wouldnt write that via polling
It not really polling, it's more like delay event firing i guess
Events from the file changes, and delaying them until a certain amount of time has passed, ignoring any other events during that wait time
The only other way I can think of doing it would be polling which I guess could work, I could work but I'd have to track the size of every file in that folder possibly, and do that every tick
i wouldnt ingore the other events
why?
i would simply kick off the gui update task with an initial delay
That's kinda what i was thinking too
that way the event listener can update the state asap and once the gui update kicks in the stuff is applied
gui update might need some additional delaying inbetween tho
That's what the 500ms delay time would be for
but at least u have 2 seperate small issues then instead of one fat complex
to prevent it freezing from updating too much
hmmm a loop might be not too bad
I'm kinda assuming the case where the app is currently viewing a folder and another application is creating/deleting files every few microseconds
Pretty much like the temp folder
but use a reset event to await changes
hmmm i need to think about it a bit
basically u need something like an awaitable auto reset event.
then ur long running update task would be
and the file change events would update the internal state and set the async auto reset event to be triggered
iirc the BCL doesnt have that tho
but instead u could use a task completion source as workaround
so
await updateTriggeredEvent.WaitAsync();
would become something like
and the file change event listeners would then trigger the update via cts.TrySetResult();
there is probably a lib out there that has an async task completen event thoI guess that could work but it still requires sitting there waiting, which means it needs its own thread or task
an alternative would be to use channels to pump the updates through, i guess https://learn.microsoft.com/en-us/dotnet/core/extensions/channels
Channels - .NET
Learn the official synchronization data structures in System.Threading.Channels for producers and consumers with .NET.
I wrote this in the mean time, no clue if it will work but oh well
async waiting doesnt need a thread
$nothread
There Is No Thread
This is an essential truth of async in its purest form: There is no thread.
but u will have a long running task somewhere, yes
Yeah that what I want to avoid is long running tasks
Just in case i end up using this behaviour all the time which I hope i don't...
well, the alternative would be to fire and forget the gui update task (which still has the delay in it), only if one isnt already running
but having that in sync would be relatively complex
the long running task will most likely not have much of an performance inpact because most of the time its waiting
so its just couple bytes (the async context) somewhere in the memory
I managed to get it without without using a thread or long running tasks: https://gist.github.com/AngryCarrot789/5867c78d1bdc75e8decd7e8627c20a2f
Gist
Executes user code no less than a certain minimum interval, every t...
Executes user code no less than a certain minimum interval, every time an input function is called - ConditionMonitor.cs
on briefly reading lgtm
I feel like it might get into a locked state in that while loop; locks, puts the condition into the state, then outside of the lock it processes the state... but then OnInput() could modify the condition just after the task leaves the lock
Either the debugger is helping me, or the race condition has a tiny window where this coud happen.. this test code has been running for about 2 mins
yeah, that part is a race condition https://gist.github.com/AngryCarrot789/5867c78d1bdc75e8decd7e8627c20a2f#file-conditionmonitor-cs-L56-L62
Gist
Executes user code no less than a certain minimum interval, every t...
Executes user code no less than a certain minimum interval, every time an input function is called - ConditionMonitor.cs
Think i might have fixed it now
tbh, just keep it simple, asked for what actually is a long running task and something like the referred code isnt
so keep it simple and probably use a channel and let that update task stay alive for the application life time
https://discord.com/channels/143867839282020352/663803973119115264/1139129758953783336 is the start of the verification
cap5lut
about long running tasks... whats actually "long running"
is it about its life time, or how long the synchronous execution is?
Quoted by
<@233139962277658624> from #advanced (click here)
React with ❌ to remove this embed.
I just didn't want to clog up the task scheduler with 100s of tasks
Even if it can handle it performance wise, still feels weird to have so many long running tasks
@cap5lut I've always found that article to be very contradicting:
Stephen Curry starts with the premisse that there is no thread blocked and waiting for the Task to finish executing, no OS Thread, no ThreadPool Thread no nothing. But later he proceeds to say that a threadpool thread is borrowed to execute the APC. If you ask me thats what most people would describe as a threadpool thread being blocked and waiting for something, something being the APC from the OS
but its not, the os does its stuff in its own threads yes, but the signaling is just a minor thing within that thread, which causes the await to finish
that os thread will run anyway, no matter if you await something or not
a threadpool thread needs to get notified of the APC. That should be blocking said threadpool thread
but that os thread wouldnt be per application, but system wide
so its running anyway
if thats blocking or polling i dunno and it doesnt really matter in this context
Sorry I meant a threadpool thread created by the Task, not some OS thread outside of our scope.
there is a Threadpool thread that waits for this APC and then sets the Task to Completed and runs any ocntinuations on the UI context. This Threadpool thread is blocked for the time until the APC is done, that makes the initial premisse false
thats not how the synchronization context is implemented afaik
The task has captured the UI context, so it does not resume the async method directly on the thread pool thread. Instead, it queues the continuation of that method onto the UI context, and the UI thread will resume executing that method when it gets around to it.
and even if it would, it would be an "one for all"
I just don't see what Stephen Curry wants to tell me here. he says there is no Thread waiting, but there is a threadpool thread waiting for the APC
he contradicts himjself
well, the threads will ofc blockingly wait for something to execute, but that doesnt mean that an
await
causes a thread to blockbut
await
does cause a thread to block! just not the Calling thread
instead it blocks a threadpool thread
thats my pointand that isnt the case is my point
so then I must be missunderstanding the point that - Threadpool thread waiting for an APC blocks said threadpool thread, how tho?
explain to me
this would never cause 100000 thread spawns+blocks
Thats due to optimizations of TaskScheduler, outside of our topic
u cant talk about them separately
ok then lets bring in the TaskScheduler too... The TaskScheduler reuses threadpool threads, but that doesn't change my point that some threadpool thread is blocked
the thread pool threads are basically (more pseudo code than anything else)
[12:12 PM]cap5lut: and even if it would, it would be an "one for all"it might be one, but not one for each
right
but then again, Stephen Curry's article is false
and im pretty sure that the runtime has some optimization to use the os scheduler instead of its own thread for such stuff
on the software level something deep down close to the bare metal has to do the polling, yes, but thats taken care of by the OS.
not by dotnet runtime
it will register a trigger + a callback to the task scheduler at the os, unless the synchronization context is poorly written
so yeah, there is a thread for that, but no, its not a thread of ur application
eehhhm from my understanding, The TaskScheduler just spawns Threads, Threads being OS Threads here. Whenever any process spawns an OS THread the OS Scheduler always comes into play. You cannot "use" it or not use it
both is involved, jsut on different layers
not to the Task Scheduler directly but to any OS Thread that got previously spawned by the Task Scheduler
the os is handling the polling (thus not dependent on any application runng( -> callbacks can be registered at the os to execute stuff on event -> these events/callbacks are used for task scheduling.
if a TaskScheduler would only be used for thread handling they wouldve been called ThreadScheduler, there is more to it than just threads...
im not sure where exactly the polling/callback invocation happens, its probably OS dependent, but for sure i can say its outside of the dotnet process and somewhere in kernel space (or mode?)
Since the library/BCL is using the standard P/Invoke overlapped I/O system, it has already registered the handle with the I/O Completion Port (IOCP), which is part of the thread pool. So an I/O thread pool thread is borrowed briefly to execute the APC, which notifies the task that it’s complete.its basically throwing in a task to schedule a task
no it wouldn't be called ThreadScheduler, as it doesn't schedule Threads, it schedules Tasks on Threads. Not sure what "more" there is to it, what do you mean?
Was this issue resolved? If so, run
/close
- otherwise I will mark this as stale and this post will be archived until there is new activity.