Git Product home page Git Product logo

Comments (12)

dodexahedron avatar dodexahedron commented on July 20, 2024 1

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.

dodexahedron avatar dodexahedron commented on July 20, 2024 1

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.

dodexahedron avatar dodexahedron commented on July 20, 2024

Yeah. We should have zero instances of Task.Delay anywhere in the code.

from terminal.gui.

BDisp avatar BDisp commented on July 20, 2024

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.

dodexahedron avatar dodexahedron commented on July 20, 2024

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.

BDisp avatar BDisp commented on July 20, 2024

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.

dodexahedron avatar dodexahedron commented on July 20, 2024

A timer is much more appropriate than sleeps, if periodic polling is the only reliable means of doing it.

from terminal.gui.

BDisp avatar BDisp commented on July 20, 2024

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.

dodexahedron avatar dodexahedron commented on July 20, 2024

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 and Task 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.

BDisp avatar BDisp commented on July 20, 2024

But with Task.Delay I'm taking advantage of using the CancellationTokenSource by catching the cancellation to exit the loop.

from terminal.gui.

dodexahedron avatar dodexahedron commented on July 20, 2024

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.

BDisp avatar BDisp commented on July 20, 2024

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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.