-
Notifications
You must be signed in to change notification settings - Fork 147
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
'Forget' extensions on ValueTask need to fake an await #556
Comments
public static void Forget(this ValueTask task) => task.AsTask(); would be the simplest. Or: public static void Forget(this ValueTask task) => task.Preserve(); is probably a tad cheaper, in particular for the |
Thanks for raising this. |
Preserve won't allocate unless the ValueTask is backed by an IValueTaskSource. |
That's good. It just seems like there ought to be a way to say "you know what, IValueTaskSource? I don't need you any more. Go be recycled now instead of later." |
Disclaimer: ValueTask's various recycling forms aren't something I'm deeply familiar with.
But there's nothing to recycle unless there's an Is this a net win? |
Finally, assuming |
It can't be recycled "now" because it could still be in flight. We could have built in a way to mark it as "when you complete, consider yourself consumed", but you can hook up the continuation yourself. It's just that unless you're in an async method where you already implicitly have the continuation object already allocated, you would need to allocate something for the callback to know which ValueTask instance to access.
Yes, but please share with me in what situation you're getting back a ValueTask that might be backed by an IValueTaskSource and where you want to "forget" it. Sounds highly dubious. APIs should really only return ValueTask if the 99.999% usage pattern is for them to be awaited. If you're hitting the allocation case in any frequency that would cause perf issues, you have a deeper problem.
https://devblogs.microsoft.com/dotnet/understanding-the-whys-whats-and-whens-of-valuetask/
Even needing a method like "Forget" for ValueTask is an anti-pattern; it should basically never be needed. And in the rare circumstance where you do call it with a method that returned a ValueTask backed by a value or a Task, there's no allocation. It's only if you're calling it with a ValueTask backed by an IValueTaskSource that you're paying for an allocation, the same allocation you would have had anyway if the method returned Task.
Write your own IValueTaskSource implementation, create a ValueTask around it, call Forget, and validate that your GetResult method is called once and only once. |
Thanks.
Would you say the same for our pre-existing I added the extension methods for |
Yes. I realize my perspective here may differ from folks who believe every single possible allocation ever should be avoided at all costs. Anyone who knows me in a professional capacity knows I care deeply about performance, but even so I think it needs to be more measured than this. My perspective is that Task should continue to be the default choice as the return type from asynchronous methods, and that ValueTask should only be used as the result of methods where:
(Stream is a good example. ReadAsync benefits significantly from using Task has fewer pitfalls than ValueTask. If you only ever await the result of an async method, then the pitfalls of ValueTask largely go away, but the problem is that consumers can easily miss the differences, and do things like await ValueTasks twice (or worse, concurrently), try to use .GetAwaiter().GetResult() to block waiting for them to complete, etc. I'm finishing up dotnet/roslyn-analyzers#3001 to help with this, but as we all know analyzers can only go so far. If one adheres to the above guidance, then there should be many, many fewer cases where you'd care to use something like Forget with ValueTask, and the need to use Forget would suggest a mistake on the part of the API developer in returning ValueTask. There are a few more cases internal to a library where it might choose to have some private method returning a ValueTask and then ignore it, but that's self-contained and the developer both authoring and consuming that same method needs to understand the implications of what they're doing. I realize there's some circular logic in the above, but it's how I think about it currently.
Can you share some details on in what situation this occurred? |
Thanks for the guidance. These extension methods are only 10 days old and haven't been inserted to VS yet AFAIK, so removing them entirely is absolutely a possibility. I'm content to remove them as it would seem to help foster these conversations with others who as you say may be over-using
I'm afraid I don't remember which repo I was working in at the time when I wished I had this. |
I was just about to add a commit to my PR to actually remove the methods, but I had another thought that makes me hesitate: Since I learned that "forgetting" a What is your recommendation, @stephentoub? I'll go either way based on your suggestion. |
I can see an argument either way. It's a bit of the tail-wagging-the-dog: my concern with Forget is really that it's blessing the producer's choice to return ValueTask in a situation where a consumer is likely to need something like Forget. Once the API creator has chosen to do so, it's not the consumers fault if they end up needing fire-and-forget, and we should help them do it correctly. So, I think it's ok to keep them, but consider adding an XML comment warning that makes it clear it's not ideal. |
Bug description
ValueTask instances can be backed by pooled objects. If these tasks are not awaited, the pooled objects can get discarded, negatively impacting application efficiency.
Repro steps
Expected behavior
The
Forget
extension methods behave as though the task is awaited. @stephentoub may be able to help identify the most efficient approach here.Actual behavior
The task is not awaited.
vs-threading/src/Microsoft.VisualStudio.Threading/TplExtensions.cs
Lines 261 to 278 in 8085d15
The text was updated successfully, but these errors were encountered: