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

Provide a built-in way to create APM methods from a Task-based method #61729

Closed
GSPP opened this issue Nov 17, 2021 · 7 comments · Fixed by #82557
Closed

Provide a built-in way to create APM methods from a Task-based method #61729

GSPP opened this issue Nov 17, 2021 · 7 comments · Fixed by #82557
Labels
api-approved API was approved in API review, it can be implemented area-System.Threading.Tasks
Milestone

Comments

@GSPP
Copy link

GSPP commented Nov 17, 2021

EDITED by @stephentoub on 1/26/2023:

We don't want to reinvigorate the legacy pattern, so we can bury these helpers now as statics on the IAsyncResult interface itself... the only reason to reach for that interface is if you're using that legacy pattern, and these methods naturally fit there:

namespace System
{
    public interface IAsyncResult
    {
+        public static IAsyncResult BeginFromTask(Task task, AsyncCallback? callback, object? state);
+        public static void EndFromTask(IAsyncResult asyncResult);
+        public static TResult EndFromTask<TResult>(IAsyncResult asyncResult);
+        public static Task UnwrapTask(IAsyncResult asyncResult);
+        public static Task<TResult> UnwrapTask<TResult>(IAsyncResult asyncResult);
    }
}

Alternatively, we can introduce a new class:

namespace System.Threading.Tasks
{
+    public static class TaskToAsyncResult
+    {
+        public static IAsyncResult Begin(Task task, AsyncCallback? callback, object? state);
+        public static void End(IAsyncResult asyncResult);
+        public static TResult End<TResult>(IAsyncResult asyncResult);
+        public static Task Unwrap(IAsyncResult asyncResult);
+        public static Task<TResult> Unwrap<TResult>(IAsyncResult asyncResult);
+    }
}

Stream provides both APM and Task methods for reading and writing. When creating a custom stream, you ideally override them both and provide a functional implementation. It is desirable to be able to implement this based on Task and possibly await just once and reuse that code for the APM methods.

The Framework does it that way in multiple places using the TaskToApm class (https://github.com/dotnet/runtime/blob/main/src/libraries/Common/src/System/Threading/Tasks/TaskToApm.cs).

I suggest that the Framework should have a built-in way to do this. Possibly, the existing TaskToApm class can just be polished and made public.

@dotnet-issue-labeler dotnet-issue-labeler bot added area-System.Threading.Tasks untriaged New issue has not been triaged by the area owner labels Nov 17, 2021
@ghost
Copy link

ghost commented Nov 17, 2021

Tagging subscribers to this area: @dotnet/area-system-threading-tasks
See info in area-owners.md if you want to be subscribed.

Issue Details

Stream provides both APM and Task methods for reading and writing. When creating a custom stream, you ideally override them both and provide a functional implementation. It is desirable to be able to implement this based on Task and possibly await just once and reuse that code for the APM methods.

The Framework does it that way in multiple places using the TaskToApm class (https://github.com/dotnet/runtime/blob/main/src/libraries/Common/src/System/Threading/Tasks/TaskToApm.cs).

I suggest that the Framework should have a built-in way to do this. Possibly, the existing TaskToApm class can just be polished and made public.

Author: GSPP
Assignees: -
Labels:

area-System.Threading.Tasks, untriaged

Milestone: -

@teo-tsirpanis
Copy link
Contributor

Why not throw NotSupportedException on user code classes overriding APM methods, especially when they only target modern .NET? How many widely used libraries are actually using APM?

@sakno
Copy link
Contributor

sakno commented Nov 30, 2021

@teo-tsirpanis , because legacy code that just works flow through various version of enterprise application. Application itself can be upgraded to later versions of .NET but still have some portions of that code. The most frequent situation is Stream. Moving from BeginXXX/EndXXX to ReadAsync/WriteAsync is not easy. For instance, the application may have two logical parts: consumer and producer of Stream. Producer can be upgraded to modern API while consumer not. In this case, the consumer still use APM methods.

In dotNext library I have to provide implementation of APM methods for various streams because it's requested by users. Also, we can do a simple query over GitHub and find related issues in SSH.NET (~2.7K watchers), websocket-sharp (~4.4K watchers) and others.

@GSPP
Copy link
Author

GSPP commented Dec 1, 2021

When a Stream fails to react correctly to a part of its contract, it fails to meet the Liskov substitution principle. In concrete cases, you can often get away with it. The cost, though, is a long-term technical debt that makes the application brittle in the face of modification. The "clean" way to go is to implement the full Stream contract and provide a high-quality, production-viable implementation of everything. Anything else is a hack that can work concretely at the cost of architectural soundness.

Concretely, even the BCL uses APM methods internally. 3rd-party libraries might as well. Company-internal code might very well do that. Abstractly, failing to provide a viable implementation for everything poses a latent risk and might cause future costs.

@joperezr joperezr added this to the Future milestone Jul 11, 2022
@ghost ghost removed the untriaged New issue has not been triaged by the area owner label Jul 11, 2022
@stephentoub stephentoub added the api-ready-for-review API is ready for review, it is NOT ready for implementation label Jan 21, 2023
@stephentoub
Copy link
Member

I've marked it as ready for review in order to have a discussion about it in API review. I'm quite hesitant to expose new APIs to make it easier to implement a very legacy pattern we don't want anyone consuming moving forward. I understand the argument that existing code still consumes it.

@stephentoub stephentoub modified the milestones: Future, 8.0.0 Jan 26, 2023
@stephentoub
Copy link
Member

Prototype: 9cc1d1d

@stephentoub stephentoub added the blocking Marks issues that we want to fast track in order to unblock other important work label Feb 14, 2023
@terrajobst
Copy link
Member

terrajobst commented Feb 23, 2023

Video

We concluded that having a separate type will be easier should we need to ship this downlevel (and there is some evidence that suggests we might want to do that).

namespace System.Threading.Tasks;

public static class TaskToAsyncResult
{
    public static IAsyncResult Begin(Task task, AsyncCallback? callback, object? state);
    public static void End(IAsyncResult asyncResult);
    public static TResult End<TResult>(IAsyncResult asyncResult);
    public static Task Unwrap(IAsyncResult asyncResult);
    public static Task<TResult> Unwrap<TResult>(IAsyncResult asyncResult);
}

@terrajobst terrajobst added api-approved API was approved in API review, it can be implemented and removed api-ready-for-review API is ready for review, it is NOT ready for implementation labels Feb 23, 2023
@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Feb 23, 2023
@stephentoub stephentoub removed the blocking Marks issues that we want to fast track in order to unblock other important work label Feb 23, 2023
@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Feb 24, 2023
@ghost ghost locked as resolved and limited conversation to collaborators Mar 26, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-approved API was approved in API review, it can be implemented area-System.Threading.Tasks
Projects
No open projects
Development

Successfully merging a pull request may close this issue.

6 participants