jsakamoto / Toolbelt.Blazor.HotKeys2

This is a class library that provides configuration-centric keyboard shortcuts for your Blazor apps.
https://jsakamoto.github.io/Toolbelt.Blazor.HotKeys2/
Mozilla Public License 2.0
87 stars 6 forks source link

Async Task and async method not working #7

Closed peterhym21 closed 1 year ago

peterhym21 commented 1 year ago

When trying to use is with a async task it does not work:

    HotKeysContext? HotKeysContext;

    protected override async Task OnInitializedAsync()
    {
        this.HotKeysContext = this.HotKeys.CreateContext()
            .Add(ModCode.None, Code.Escape, Cancel);
    }

    async Task Cancel() => await BlazoredModal.CancelAsync();

gives these errors

image

works fine if I use a void method

@jsakamoto any help?

jsakamoto commented 1 year ago

@peterhym21 The HotKeys2 doesn't accept functions that return a Task. This is by design. Please use functions that return a ValueTask instead, like this:

async ValueTask Cancel() => await BlazoredModal.CancelAsync();
      // 👆 Use `ValueTask` instead of `Task`.
peterhym21 commented 1 year ago

Hi @jsakamoto

Thanks for the help, but I don't understand why this is by design can you maybe clarify and help me to fined a better solution for my problem? In advance thanks :)

Because now you cant use it in with a Task Like async Task OK() => await BlazoredModal.CloseAsync(); witch is a existing method for a Button. so u have to make a new method for just the HotKey

see below for old and new HotKeys versions code that I am talking about

Old Hotkeys

@inject HotKeys HotKeys
@implements IDisposable

<div class="modal ">
    <div class="modal-content bg-danger">
        <div class="modal-header">
            <h3 class="modal-title">Error</h3>
        </div>
        <div class="modal-body">
            <p class="message">@Message</p>
        </div>
        <div class="modal-footer">
            <RadzenButton Click="OK" ButtonStyle="ButtonStyle.Primary" class="modal-button">OK</RadzenButton>
            <RadzenButton Click="Cancel" ButtonStyle="ButtonStyle.Light" class="modal-button">Cancel</RadzenButton>
        </div>
    </div>
</div>

@code {
    [CascadingParameter] BlazoredModalInstance BlazoredModal { get; set; } = default!;
    [Parameter] public string? Message { get; set; }

    HotKeysContext? HotKeysContext;

    protected override async Task OnInitializedAsync()
    {
        this.HotKeysContext = this.HotKeys.CreateContext()
            .Add(ModKeys.None, Keys.ESC, Cancel);
    }

    async Task OK() => await BlazoredModal.CloseAsync();
    async Task Cancel() => await BlazoredModal.CancelAsync();

    public void Dispose() => this.HotKeysContext.Dispose();
}

New HotKeys2

@inject HotKeys HotKeys
@implements IDisposable

<div class="modal ">
    <div class="modal-content bg-danger">
        <div class="modal-header">
            <h3 class="modal-title">Error</h3>
        </div>
        <div class="modal-body">
            <p class="message">@Message</p>
        </div>
        <div class="modal-footer">
            <RadzenButton Click="OK" ButtonStyle="ButtonStyle.Primary" class="modal-button">OK</RadzenButton>
            <RadzenButton Click="Cancel" ButtonStyle="ButtonStyle.Light" class="modal-button">Cancel</RadzenButton>
        </div>
    </div>
</div>

@code {
    [CascadingParameter] BlazoredModalInstance BlazoredModal { get; set; } = default!;
    [Parameter] public string? Message { get; set; }

    HotKeysContext? HotKeysContext;

    protected override async Task OnInitializedAsync()
    {
        this.HotKeysContext = this.HotKeys.CreateContext()
            .Add(ModCode.None, Code.Escape, CancelHotkey);
    }

    async Task OK() => await BlazoredModal.CloseAsync();
    async Task Cancel() => await BlazoredModal.CancelAsync();
    async ValueTask CancelHotkey() => await BlazoredModal.CancelAsync();

    public void Dispose() => this.HotKeysContext.Dispose();
}
jsakamoto commented 1 year ago

Hi @peterhym21,

why this is by design

The first reason is "performance". ValueTask is a struct, not a class, so it doesn't need to be allocated from the heap area. That means ValueTask never causes garbage collection. This is important, particularly in the Blazor WebAssembly scenario, because usually, Blazor WebAssembly is driven by MSIL interpreter, not native processor speed.

See also: "ValueTask vs. Task" https://medium.com/@karol.rossa/valuetask-vs-task-5fb4f9c6517

The second reason is that I don't want to spend much time implementing the HotKeys2 library. If I'm implementing Task support, I will need a lot of time to implement many Add methods of overload versions and unit tests to test them. Currently, I've wanted to keep working on another of my hobby product.

Those are reasons "why this is by design".

By the way, if you must the callback method be a Task async method, you can use it by the following syntax.

...
@code {
    ...
    protected override async Task OnInitializedAsync()
    {
        this.HotKeysContext = this.HotKeys.CreateContext()

            // Wrap your `Task` async method with 👇 async/await lambda like below.
            .Add(ModCode.None, Code.Escape, async () => await Cancel());
    }
    ...
    async Task Cancel() => await BlazoredModal.CancelAsync();
    ...

Moreover, you can also implement your custom extension Add method, which accepts a Task async method by yourself.

P.S. There are Add methods overload versions that don't have a ModCode or a ModKey argument. For example, if the ModCode argument is None like this:

    .Add(ModCode.None, Code.Escape, async () => await Cancel());

You can rewrite it shorter without the ModCode argument like below:

    .Add(Code.Escape, async () => await Cancel());

Happy Coding!