Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SDLPrinter.Print in addition to SDLPrinter.PrintAsync #275

Closed
nightroman opened this issue Oct 20, 2022 · 8 comments
Closed

SDLPrinter.Print in addition to SDLPrinter.PrintAsync #275

nightroman opened this issue Oct 20, 2022 · 8 comments
Labels
enhancement New feature or request
Milestone

Comments

@nightroman
Copy link

Is your feature request related to a problem? Please describe.

Having only SDLPrinter.PrintAsync is inconvenient in synchronous scenarios.

Not usually recommended Result or GetAwaiter().GetResult() seemingly works
(so far? by chance?) but produces the code analysis issue / warning CA2012:

ValueTask instances should not have their result directly accessed unless the
instance has already completed. Unlike Tasks, calling Result or
GetAwaiter().GetResult() on a ValueTask is not guaranteed to block until the
operation completes. If you can't simply await the instance, consider first
checking its IsCompleted property (or asserting it's true if you know that to
be the case).

The recommended above safe way of getting ValueTask result is somewhat convoluted.

Describe the solution you'd like

The synchronous SDLPrinter.Print free of potential issues would be handy.

@Shane32
Copy link
Member

Shane32 commented Oct 20, 2022

Agreed. Unfortunately this would duplicate almost all of the code in both ASTVisitor.cs and SDLPrinter.cs. FYI if you’re writing to a string builder or MemoryStream, I believe GetAwaiter().GetResult() is entirely safe to use because it will run synchronously regardless. But if you’re writing to a file stream or network stream, then there may be multithreading implications (although unlikely for most use cases).

@Shane32
Copy link
Member

Shane32 commented Oct 20, 2022

@Shane32 Shane32 added the enhancement New feature or request label Oct 20, 2022
@Shane32
Copy link
Member

Shane32 commented Oct 20, 2022

And it would necessitate duplicating all of the relevant tests for the AST walker and SDL printer. It's just a whole lot of code duplication to properly add synchronous support.

Perhaps a temporary solution might be to add a Print method that accepts a StringBuilder (and/or returns a string). Perhaps also add an overload that accepts a MemoryStream and Encoding to write to a memory stream. These methods could simply use GetAwaiter().GetResult() under the hood but would be known to execute synchronously and hence be free from ill effects.

@nightroman
Copy link
Author

nightroman commented Oct 20, 2022

Would replacing ValueTask with Task be a solution, too? I am not that familiar with ValueTask. Quick googling tells it is used in order to improve performance and should be used only when it really improves performance. Because ValueTask introduces some trade-offs (as we see here, for example). Also, in some cases (they say) Task may actually perform better.

@nightroman
Copy link
Author

Perhaps a temporary solution might be to add a Print method that accepts a StringBuilder (and/or returns a string). Perhaps also add an overload that accepts a MemoryStream and Encoding to write to a memory stream. These methods could simply use GetAwaiter().GetResult() under the hood but would be known to execute synchronously and hence be free from ill effects.

It looks like a good solution, straightforward for the end user, with all tedious caveats resolved internally.

@Shane32
Copy link
Member

Shane32 commented Oct 20, 2022

ValueTask<T> reduces allocations when the method runs synchronously. Typically this does not occur when using asynchronous methods such as writing to a file or network stream, but even then might if reading/writing cached data. However in this case where the underlying operation is often (or typically) writing to a string builder, there are notable performance benefits to ValueTask<T>.

Note that performance benefits of ValueTask over Task are very rare, as typically the static instance of Task.CompletedTask is returned from synchronously-completed asynchronous methods that return Task. However in this library, ValueTask was used throughout for consistency.

While there are some limitations with ValueTask, there are no benefits or consequences to ValueTask over Task as it relates to executing asynchronous code synchronously.

You may be interested in these relevant articles on the use of ValueTask and allocations:

Notably, while .NET Framework might require up to 4 allocations the first time await is encountered, and 2 for each additional await in the same method, .NET Core reduces this to 1 (per method I believe), so the benefits of using ValueTask on .NET Core is less pronounced than on .NET Framework (but still exist).

That means, whereas in .NET Framework we have at least four allocations for the first suspension and often at least two allocations for each subsequent suspension, in .NET Core we have one allocation for the first suspension (worst case two, if custom awaiters are used), and that’s it.

Just FYI

@nightroman
Copy link
Author

@Shane32 Thank you for taking an action on this. Looking forward to the next version with Print(s).

@sungam3r
Copy link
Member

@sungam3r sungam3r added this to the 8.2 milestone Apr 16, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants