Skip to content

Conversation

@tmds
Copy link
Contributor

@tmds tmds commented Mar 9, 2023

@patriksvensson ptal.


Please upvote 👍 this pull request if you are interested in it.

@tmds
Copy link
Contributor Author

tmds commented Apr 13, 2023

@patriksvensson can you take a look?

@tmds
Copy link
Contributor Author

tmds commented Jun 13, 2023

@patriksvensson ptal.

@patriksvensson
Copy link
Contributor

@tmds Sorry for taking time with this; currently a bit behind on the backlog. Will try to take a look at this tonight.

@mbursill
Copy link

This is very useful. Would love to see it merged.

@github-actions github-actions bot added the ⭐ top pull request Top pull request. label Apr 24, 2024
@FrankRay78 FrankRay78 changed the title [I]AnsiConsole: add async overloads for Prompt/Ask/Confirm. AnsiConsole: add async overloads for Prompt/Ask/Confirm. Jul 31, 2024
@FrankRay78 FrankRay78 changed the title AnsiConsole: add async overloads for Prompt/Ask/Confirm. Async overloads for AnsiConsole Prompt/Ask/Confirm. Jul 31, 2024
@FrankRay78 FrankRay78 self-assigned this Jul 31, 2024
@FrankRay78 FrankRay78 added this to the 0.49 milestone Jul 31, 2024
@FrankRay78 FrankRay78 self-requested a review July 31, 2024 11:00
@FrankRay78
Copy link
Contributor

I'll take a look at this @tmds

@FrankRay78
Copy link
Contributor

Dear @spectreconsole/maintainers, what's our policy on unit test coverage when adding Async overloads to existing functionality.

The code in this PR is clear, well structured, and high-quality. However, it doesn't contain any unit tests.

We have extensive unit test coverage of prompts here: test/Spectre.Console.Tests/Unit/Prompts, however they exercise the current (non-async) calls.

I don't think it makes sense to duplicate the 100-something existing unit tests for these async extensions, however equally I don't want to merge this until I've checked it's ok 'as-is' without tests.

What are your thoughts?

Copy link
Contributor

@FrankRay78 FrankRay78 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good quality contribution, however waiting on feedback as to whether we need unit test coverage when adding async overloads.

@FrankRay78
Copy link
Contributor

This is very useful. Would love to see it merged.

@mbursill Do you have a code example of how you'd like to use the async overloads, out of interest?

@patriksvensson patriksvensson modified the milestones: 0.49, 0.50 Sep 2, 2024
@FrankRay78
Copy link
Contributor

@tmds - what is the usage for an async prompt? I need some help understanding why a synchronous user prompt should be extended to be async. I'm not saying there isn't a need, but I need a working code snippet illustrating the motivation behind this PR please.

@tmds
Copy link
Contributor Author

tmds commented Sep 5, 2024

@tmds - what is the usage for an async prompt? I need some help understanding why a synchronous user prompt should be extended to be async. I'm not saying there isn't a need, but I need a working code snippet illustrating the motivation behind this PR please.

The CancellationToken I'm passing is the one from https://github.com/dotnet/command-line-api that informs me the user has requested the app to terminate via Ctrl+C. As part of the termination, I want some things to get cleaned-up/Disposed.

@FrankRay78
Copy link
Contributor

@tmds ok thanks for replying after so much time. Apologies, the widgets aren't my usual area of expertise and the other maintainers are quite busy to ask. I noticed your PR which looked like a good quality contribution and taken it on myself to review/merge. I think I understand the usage now. I do plan to get this one over the line.

@tmds
Copy link
Contributor Author

tmds commented Sep 6, 2024

No worries, @FrankRay78. Thanks for picking this up!

@patriksvensson
Copy link
Contributor

@FrankRay78 Unit tests are preferred so we'll know if we introduce regressions. But it's your call.

0xced added a commit to 0xced/spectre.console that referenced this pull request Sep 10, 2024
The current prompt APIs suffer from several problems.

First, the prompt APIs are fundamentally synchronous APIs. If we get down to the underlying implementation of all the prompt APIs, i.e. the `DefaultInput` class we can see some issues.

Here's are the problematic implementation (before this commit fixes it):

```csharp
public ConsoleKeyInfo? ReadKey(bool intercept)
{
    return System.Console.ReadKey(intercept);
}

public async Task<ConsoleKeyInfo?> ReadKeyAsync(bool intercept, CancellationToken cancellationToken)
{
    while (true)
    {
        if (cancellationToken.IsCancellationRequested)
        {
            return null;
        }

        if (System.Console.KeyAvailable)
        {
            break;
        }

        await Task.Delay(5, cancellationToken).ConfigureAwait(false);
    }

    return ReadKey(intercept);
}
```

* The syncrhonous `ReadKey` method returns a nullable `ConsoleKeyInfo` struct but `System.Console.ReadKey` can never return a nullable `ConsoleKeyInfo`.
* The asyncrhonous `ReadKeyAsync` method can return `null` only if cancellation has been requested. But this can never actually happen since [Fix deadlock when cancelling prompts (spectreconsole#1439)](spectreconsole#1439) was merged.
* The asyncrhonous `ReadKeyAsync` method is not actually an synchronous method, it's waiting for a key to be pressed in a loop, waiting 5 milliseconds before checking again if it can break out of the loop.

The proposed fix obsoletes the `ReadKeyAsync` method and add cancellation support to the synchronous `ReadKey` method through an optional  `CancellationToken`. It also returns a non-nullable `ConsoleKeyInfo` making it clear that the only way to get out of this method is through cancellation. Then this change bubbles up to all the prompt APIs, also obsoleting the `IPrompt.ShowAsync` methods.

This is a better alternative to [Async overloads for AnsiConsole Prompt/Ask/Confirm (spectreconsole#1194)](spectreconsole#1194) where the actual need is having a `CancellationToken` to perform some cleanup and not having async prompt APIs.

Note that this is a breaking change since it modifies the signatures of the public `IAnsiConsoleInput` interface but that should not be an issue since it's impossible to use another implentation than `DefaultInput` when used through `AnsiConsole.Create(AnsiConsoleSettings settings)`.  

I have also searched for [implementers of IAnsiConsoleInput](https://grep.app/search?q=IAnsiConsoleInput) and I think this change won't break anything since nobody actually implemented `IAnsiConsoleInput`. Only exising implementations which have been udated are being used (at least across a half million public git repos).
0xced added a commit to 0xced/spectre.console that referenced this pull request Sep 10, 2024
The current prompt APIs suffer from several problems.

First, the prompt APIs are fundamentally synchronous APIs. If we get down to the underlying implementation of all the prompt APIs, i.e. the `DefaultInput` class we can see some issues.

Here's are the problematic implementation (before this commit fixes it):

```csharp
public ConsoleKeyInfo? ReadKey(bool intercept)
{
    return System.Console.ReadKey(intercept);
}

public async Task<ConsoleKeyInfo?> ReadKeyAsync(bool intercept, CancellationToken cancellationToken)
{
    while (true)
    {
        if (cancellationToken.IsCancellationRequested)
        {
            return null;
        }

        if (System.Console.KeyAvailable)
        {
            break;
        }

        await Task.Delay(5, cancellationToken).ConfigureAwait(false);
    }

    return ReadKey(intercept);
}
```

* The syncrhonous `ReadKey` method returns a nullable `ConsoleKeyInfo` struct but `System.Console.ReadKey` can never return a nullable `ConsoleKeyInfo`.
* The asyncrhonous `ReadKeyAsync` method can return `null` only if cancellation has been requested. But this can never actually happen since [Fix deadlock when cancelling prompts (spectreconsole#1439)](spectreconsole#1439) was merged.
* The asyncrhonous `ReadKeyAsync` method is not actually an synchronous method, it's waiting for a key to be pressed in a loop, waiting 5 milliseconds before checking again if it can break out of the loop.

The proposed fix obsoletes the `ReadKeyAsync` method and add cancellation support to the synchronous `ReadKey` method through an optional  `CancellationToken`. It also returns a non-nullable `ConsoleKeyInfo` making it clear that the only way to get out of this method is through cancellation. Then this change bubbles up to all the prompt APIs, also obsoleting the `IPrompt.ShowAsync` methods.

This is a better alternative to [Async overloads for AnsiConsole Prompt/Ask/Confirm (spectreconsole#1194)](spectreconsole#1194) where the actual need is having a `CancellationToken` to perform some cleanup and not having async prompt APIs.

Note that this is a breaking change since it modifies the signatures of the public `IAnsiConsoleInput` interface but that should not be an issue since it's impossible to use another implentation than `DefaultInput` when used through `AnsiConsole.Create(AnsiConsoleSettings settings)`.  

I have also searched for [implementers of IAnsiConsoleInput](https://grep.app/search?q=IAnsiConsoleInput) and I think this change won't break anything since nobody actually implemented `IAnsiConsoleInput`. Only exising implementations which have been udated are being used (at least across a half million public git repos).

The addition of the `CancellationToken` to `IPrompt.Show(IAnsiConsole console, CancellationToken cancellationToken = default)` is also a breaking change but it should be mitigated since it has bee introduced with a default value.
0xced added a commit to 0xced/spectre.console that referenced this pull request Sep 10, 2024
The current prompt APIs suffer from several problems.

First, the prompt APIs are fundamentally synchronous APIs. If we get down to the underlying implementation of all the prompt APIs, i.e. the `DefaultInput` class we can see some issues.

Here's are the problematic implementation (before this commit fixes it):

```csharp
public ConsoleKeyInfo? ReadKey(bool intercept)
{
    return System.Console.ReadKey(intercept);
}

public async Task<ConsoleKeyInfo?> ReadKeyAsync(bool intercept, CancellationToken cancellationToken)
{
    while (true)
    {
        if (cancellationToken.IsCancellationRequested)
        {
            return null;
        }

        if (System.Console.KeyAvailable)
        {
            break;
        }

        await Task.Delay(5, cancellationToken).ConfigureAwait(false);
    }

    return ReadKey(intercept);
}
```

* The syncrhonous `ReadKey` method returns a nullable `ConsoleKeyInfo` struct but `System.Console.ReadKey` can never return a nullable `ConsoleKeyInfo`.
* The asyncrhonous `ReadKeyAsync` method can return `null` only if cancellation has been requested. But this can never actually happen since [Fix deadlock when cancelling prompts (spectreconsole#1439)](spectreconsole#1439) was merged.
* The asyncrhonous `ReadKeyAsync` method is not actually an synchronous method, it's waiting for a key to be pressed in a loop, waiting 5 milliseconds before checking again if it can break out of the loop.

The proposed fix obsoletes the `ReadKeyAsync` method and add cancellation support to the synchronous `ReadKey` method through an optional  `CancellationToken`. It also returns a non-nullable `ConsoleKeyInfo` making it clear that the only way to get out of this method is through cancellation. Then this change bubbles up to all the prompt APIs, also obsoleting the `IPrompt.ShowAsync` methods.

This is a better alternative to [Async overloads for AnsiConsole Prompt/Ask/Confirm (spectreconsole#1194)](spectreconsole#1194) where the actual need is having a `CancellationToken` to perform some cleanup and not having async prompt APIs.

Note that this is a breaking change since it modifies the signatures of the public `IAnsiConsoleInput` interface but that should not be an issue since it's impossible to use another implentation than `DefaultInput` when used through `AnsiConsole.Create(AnsiConsoleSettings settings)`.  

I have also searched for [implementers of IAnsiConsoleInput](https://grep.app/search?q=IAnsiConsoleInput) and I think this change won't break anything since nobody actually implemented `IAnsiConsoleInput`. Only exising implementations which have been udated are being used (at least across a half million public git repos).

The addition of the `CancellationToken` to `IPrompt.Show(IAnsiConsole console, CancellationToken cancellationToken = default)` is also a breaking change but it should be mitigated since it has bee introduced with a default value.
0xced added a commit to 0xced/spectre.console that referenced this pull request Sep 10, 2024
The current prompt APIs suffer from several problems.

First, the prompt APIs are fundamentally synchronous APIs. If we get down to the underlying implementation of all the prompt APIs, i.e. the `DefaultInput` class we can see some issues.

Here's are the problematic implementation (before this commit fixes it):

```csharp
public ConsoleKeyInfo? ReadKey(bool intercept)
{
    return System.Console.ReadKey(intercept);
}

public async Task<ConsoleKeyInfo?> ReadKeyAsync(bool intercept, CancellationToken cancellationToken)
{
    while (true)
    {
        if (cancellationToken.IsCancellationRequested)
        {
            return null;
        }

        if (System.Console.KeyAvailable)
        {
            break;
        }

        await Task.Delay(5, cancellationToken).ConfigureAwait(false);
    }

    return ReadKey(intercept);
}
```

* The syncrhonous `ReadKey` method returns a nullable `ConsoleKeyInfo` struct but `System.Console.ReadKey` can never return a nullable `ConsoleKeyInfo`.
* The asyncrhonous `ReadKeyAsync` method can return `null` only if cancellation has been requested. But this can never actually happen since [Fix deadlock when cancelling prompts (spectreconsole#1439)](spectreconsole#1439) was merged.
* The asyncrhonous `ReadKeyAsync` method is not actually an synchronous method, it's waiting for a key to be pressed in a loop, waiting 5 milliseconds before checking again if it can break out of the loop.

The proposed fix obsoletes the `ReadKeyAsync` method and add cancellation support to the synchronous `ReadKey` method through an optional  `CancellationToken`. It also returns a non-nullable `ConsoleKeyInfo` making it clear that the only way to get out of this method is through cancellation. Then this change bubbles up to all the prompt APIs, also obsoleting the `IPrompt.ShowAsync` methods.

This is a better alternative to [Async overloads for AnsiConsole Prompt/Ask/Confirm (spectreconsole#1194)](spectreconsole#1194) where the actual need is having a `CancellationToken` to perform some cleanup and not having async prompt APIs.

Note that this is a breaking change since it modifies the signatures of the public `IAnsiConsoleInput` interface but that should not be an issue since it's impossible to use another implentation than `DefaultInput` when used through `AnsiConsole.Create(AnsiConsoleSettings settings)`.  

I have also searched for [implementers of IAnsiConsoleInput](https://grep.app/search?q=IAnsiConsoleInput) and I think this change won't break anything since nobody actually implemented `IAnsiConsoleInput`. Only exising implementations which have been udated are being used (at least across a half million public git repos).

The addition of the `CancellationToken` to `IPrompt.Show(IAnsiConsole console, CancellationToken cancellationToken = default)` is also a breaking change but it should be mitigated since it has bee introduced with a default value.
0xced added a commit to 0xced/spectre.console that referenced this pull request Sep 11, 2024
The current prompt APIs suffer from several problems.

First, the prompt APIs are fundamentally synchronous APIs. If we get down to the underlying implementation of all the prompt APIs, i.e. the `DefaultInput` class we can see some issues.

Here's are the problematic implementation (before this commit fixes it):

```csharp
public ConsoleKeyInfo? ReadKey(bool intercept)
{
    return System.Console.ReadKey(intercept);
}

public async Task<ConsoleKeyInfo?> ReadKeyAsync(bool intercept, CancellationToken cancellationToken)
{
    while (true)
    {
        if (cancellationToken.IsCancellationRequested)
        {
            return null;
        }

        if (System.Console.KeyAvailable)
        {
            break;
        }

        await Task.Delay(5, cancellationToken).ConfigureAwait(false);
    }

    return ReadKey(intercept);
}
```

* The syncrhonous `ReadKey` method returns a nullable `ConsoleKeyInfo` struct but `System.Console.ReadKey` can never return a nullable `ConsoleKeyInfo`.
* The asyncrhonous `ReadKeyAsync` method can return `null` only if cancellation has been requested. But this can never actually happen since [Fix deadlock when cancelling prompts (spectreconsole#1439)](spectreconsole#1439) was merged.
* The asyncrhonous `ReadKeyAsync` method is not actually an synchronous method, it's waiting for a key to be pressed in a loop, waiting 5 milliseconds before checking again if it can break out of the loop.

The proposed fix obsoletes the `ReadKeyAsync` method and add cancellation support to the synchronous `ReadKey` method through an optional  `CancellationToken`. It also returns a non-nullable `ConsoleKeyInfo` making it clear that the only way to get out of this method is through cancellation. Then this change bubbles up to all the prompt APIs, also obsoleting the `IPrompt.ShowAsync` methods.

This is a better alternative to [Async overloads for AnsiConsole Prompt/Ask/Confirm (spectreconsole#1194)](spectreconsole#1194) where the actual need is having a `CancellationToken` to perform some cleanup and not having async prompt APIs.

Note that this is a breaking change since it modifies the signatures of the public `IAnsiConsoleInput` interface but that should not be an issue since it's impossible to use another implentation than `DefaultInput` when used through `AnsiConsole.Create(AnsiConsoleSettings settings)`.  

I have also searched for [implementers of IAnsiConsoleInput](https://grep.app/search?q=IAnsiConsoleInput) and I think this change won't break anything since nobody actually implemented `IAnsiConsoleInput`. Only exising implementations which have been udated are being used (at least across a half million public git repos).

The addition of the `CancellationToken` to `IPrompt.Show(IAnsiConsole console, CancellationToken cancellationToken = default)` is also a breaking change but it should be mitigated since it has bee introduced with a default value.
@0xced
Copy link
Contributor

0xced commented Sep 11, 2024

We've been discussing with @FrankRay78 and I've been experimenting with an alternative approach to this issue. I think the async APIs actually don't make sense but the sync APIs need cancellation support. I've been working on an obsolete-async-prompt-apis branch where I have added cancellation support for all the prompt APIs.

@tmds Would you mind checking out this branch, build from source and see if this approach solves your problem? If you need help building from source I can assist.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⭐ top pull request Top pull request.

Projects

Status: PR 📬

Development

Successfully merging this pull request may close these issues.

6 participants