Comments (12)
Just to show how significant seemingly small details like this really can be, here's a comparison of how much more actual code is created by the await Task.Delay
method vs the timer method.
Take the following two simple applications that do essentially the same thing:
// This is in both applications and is simply what keeps it from exiting immediately.
private static readonly ManualResetEventSlim _appKeepalive = new();
public static async Task Main()
{
while(!_appKeepalive.IsSet)
{
StuffInsideLoop();
await Task.Delay(250).ConfigureAwait(true);
}
}
private static void StuffInsideLoop()
{
ConsoleKeyInfo key = Console.ReadKey(true);
Console.Out.Write(key.KeyChar);
if (key is { Key: ConsoleKey.Q })
{
_appKeepalive.Set();
}
}
And using System.Timers.Timer:
public static class Program
{
private static readonly System.Timers.Timer _eventDispatchTimer = new(250){ AutoReset = true};
// This is in both applications and is simply what keeps it from exiting immediately.
private static readonly ManualResetEventSlim _appKeepalive = new();
public static void Main()
{
_eventDispatchTimer.Elapsed+= StuffInsideLoop;
_eventDispatchTimer.Start();
_appKeepalive.Wait( -1 );
}
private static void StuffInsideLoop(object? sender, ElapsedEventArgs e)
{
ConsoleKeyInfo key = Console.ReadKey(true);
Console.Out.Write(key.KeyChar);
if (key is { Key: ConsoleKey.Q })
{
_appKeepalive.Set();
}
}
}
The first results in 13.5kB of CIL.
The second results in 12.5kB of CIL.
There's a lot of extra stuff going on for just that little tiny program.
And when I compile down to native, the first one is 8kB larger than the second, when optimized and without debug symbols included. That's a lot of executable code, and a lot of run-time resources as well.
These effects will be even more significant in an application doing more than just this, like anything referencing Terminal.Gui.
from terminal.gui.
That's fine with me.
When we get closer to or maybe actually in beta, I'll be happy to take a performance pass over things like this.
from terminal.gui.
Yeah. We should have zero instances of Task.Delay anywhere in the code.
from terminal.gui.
Yeah. We should have zero instances of Task.Delay anywhere in the code.
Yes it suits. The only one I can't avoid is the CheckWindowSizeChange
method because it is necessary to monitor the console resizing. But now it is using async Task and thus it is respecting the elapsed time.
from terminal.gui.
We can probably use (and probably should use) something like (Auto|Manual)ResetEvent or another synchronization primitive for that, rather than sleeps in method calls that may or may not execute on another thread or even asynchronously at all (you can't control it when using the TAP like this).
from terminal.gui.
There is already ManualResetEvent for those we can control using like a switch. For the console resizing we have to monitoring periodically.
from terminal.gui.
A timer is much more appropriate than sleeps, if periodic polling is the only reliable means of doing it.
from terminal.gui.
A timer is much more appropriate than sleeps, if periodic polling is the only reliable means of doing it.
I opted for a simpler solution taking advantage of the cancellation token by just adding the Wait
method after Task.Delay(int, CancellationToken).Wait(CancellationToken)
. This way the set time is respected and does not return immediately or returns immediately if the cancellation token is cancelled.
from terminal.gui.
It's not really simpler, though.
- It's a lot more code, especially after generation.
- It's much heavier, due to what is generated for everything about
async
andTask
operations. - It has no guarantee of how it will run. Might be synchronous; Might be asynchronous. You can't control it).
- If it errors out, it is unrecoverable without a watchdog to restart it.
- It still uses a timer, anyway. However, it is significantly less efficient, as it creates and destroys one each time.
- Awaiting a Task.Delay in a loop is extra code (both written and generated) achieving the same outcome as a Thread.Sleep in a loop.
Instead, simply have the application own a System.Timers.Timer object, which sits there free-running.
- Takes one line to create, one line to subscribe a handler, one line to start, and one line to stop.
- Whatever code would otherwise be inside the loop just goes in the handler.
- An exception doesn't stop it from running.
- The Elapsed event can be subscribed to by anything else that might want periodic dispatch, too, without need for any other infrastructure.
- Timing and dispatch of the Elapsed event are not dependent on the code inside the handlers.
- The timer can be suspended and resumed at will, if necessary, at any point, with a simple Start or Stop call.
from terminal.gui.
But with Task.Delay
I'm taking advantage of using the CancellationTokenSource
by catching the cancellation to exit the loop.
from terminal.gui.
You don't even need it with a timer. You simply call Stop or Dispose it or, if the application is exiting, let it die on its own with the AppDomain.
But you can pass a CT to it if you want, anyway. But it'd be more advantageous to register for the cancellation callback from the CTS, rather than making a new CT each call, which is what happens.
And, wanna know how CancellationTokenSource is implemented? A Timer and a ManualResetEvent, plus a couple other WaitHandles for synchronizing things like the registration callback list (which is an event, but with specific synchronization behavior.
But CT is a readonly struct. Every time you pass it, you're making at least one copy.
Besides, that's very improper use of the CT and of the Task itself, anyway. You do not return from a canceled task. You throw OperationCanceledException. Returning can result in resource leaks and synchronization issues that can't be traced because you've thrown the handle away without letting the caller know what happene. At that point, a Task is completely pointless and you may as well just use ThreadPool.QueueUserWorkItem instead (don't do that).
from terminal.gui.
But you can pass a CT to it if you want, anyway. But it'd be more advantageous to register for the cancellation callback from the CTS, rather than making a new CT each call, which is what happens.
It's not making a new CT anymore. In this commit e03b20e I made it as readonly, like I did in the v1 on #3519.
But I believe we can achieve better result with a timer but I prefer you implement it because I'm not prepared to do that as well as you.
from terminal.gui.
Related Issues (20)
- Disable borders on a MenuItem HOT 4
- v2 crashes on run with missing dependency JetBrains.Annotations HOT 33
- When adding more than one Window + TextView together with a MenuBar, the displayed text changes to the text of the first added Window HOT 4
- Create custom commands/key bindings HOT 3
- Mouse is broken in v2 HOT 2
- v2: Big performance impact on ListView/ComboBox's that contain a lot of items HOT 3
- Multiple views with the same `HotKey` should be supported HOT 1
- Nuget.Config for v2 can behave badly for local CLI builds, plus some minor QoL stuff for Terminal.Gui.csproj and the solution
- Build .... oddities... HOT 4
- Make the PowerShell modules arch-agnostic for now HOT 3
- Fail gracefully for impossible hosts HOT 4
- Rename `StateChangedEventArgs<T>` to `CancelEventArgs<T>` HOT 63
- A more reliable means of avoiding errors caused by stdin or stdout redirection
- (Internal) Better regexes for TODO-list items
- Clicking the StatusBar causes the event to be executed more than once. HOT 1
- `Dim.Auto (Content)` is broken if subviews use `Pos.Center` etc... HOT 3
- `StatusBar` should clip, not overwrite, when last item doesn't fit HOT 1
- `TextField` prevents default `Button` from working in some cases HOT 7
- Some more prep for revamped sourcegens and CI HOT 2
- Enabling trim mode breaks program at runtime (v2 only) HOT 16
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from terminal.gui.