5 ms·
Also add structured concurrency to the list - it's a subset of functionality that arises from simple usage of Task composition and async/await since their intro
by neonsunset 2y ago
Also add structured concurrency to the list - it's a subset of functionality that arises from simple usage of Task composition and async/await since their introduction.
Other than that, note that Panama Vectors are in a rough shape at this moment. Perhaps more feedback from users will make the authors realize that both API and implementation are flawed and map poorly onto many critical parts of algorithm implementations, particularly shuffles. They require a lot more API changes and implementation work to catch up to the current state of Vector APIs in .NET.
Of course the reality of not having access to stack buffers and fast off-heap memory allocation/deallocation also hinders the usability.
In the end, both of these will be at least partially mitigated with pain of engineers who work on e.g. Lucene, but it's something that comes to C# "naturally" and is easily accessible to a developer that does not have as much prerequisite knowledge.
- CharlieDigital 2y ago> Also add structured concurrency to the list - it's a subset of functionality that naturally arises Task composition and async/await since their introduction. Could be wrong, but the JEP feels like it goes a bit deeper than `async/await` in C#. It feels like a hybrid between Task and Channels but operating at a lower level.
- neonsunset 2y ago> Could be wrong, but the JEP feels like it goes a bit deeper than `async/await` in C#. It feels like a hybrid between Task and Channels but operating at a lower level. Reading the proposal again, it's difficult to agree with this. The proposal talks about concepts that occur naturally when writing asynchronous code with C# (or F#, which has its own pros and cons, but mostly nicer combinators). For example: Response handle() throws ExecutionException, InterruptedException { try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { Supplier<String> user = scope.fork(() -> findUser()); Supplier<Integer> order = scope.fork(() -> fetchOrder()); scope.join() // Join both subtasks .throwIfFailed(); // ... and propagate errors // Here, both subtasks have succeeded, so compose their results return new Response(user.get(), order.get()); } } becomes the following in C# async Task<Response> Handle() { var user = FindUser(); var order = FindOrder(); return new(await user, await order); } For the happy path, the difference can be more drastic <T> List<T> runAll(List<Callable<T>> tasks) throws InterruptedException, ExecutionException { try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { List<? extends Supplier<T>> suppliers = tasks.stream().map(scope::fork).toList(); scope.join() .throwIfFailed(); // Propagate exception if any subtask fails // Here, all tasks have succeeded, so compose their results return suppliers.stream().map(Supplier::get).toList(); } turns into var results = await Task.WhenAll(tasks); // Or, assuming .GetUser returns Task<GitHubUser> var names = new[] { "java", "dotnet", "golang", "rust-lang", "python" }; var repos = await Task.WhenAll( names.Select(n => GitHubApi.GetUser(n)); And so on and so forth. You can mix and match more complicated flows with CancellationToken and its CancellationTokenSource, TaskCompletionSource, Task.WhenAll/Any/Each (the last one transforms a collection of tasks into an asynchronous sequence of results). Apply exception handling at per-task or task collection level for additional customization. All of this occurs by composing the building blocks, rather than needing complex and bespoke API.
- CharlieDigital 2y agoI still don't think it's an exact analogue and has more of a flavor of System.Threading.Channels. In particular, when you look at the proposal for fan-in.
- neonsunset 2y agoFan-in, as described in the proposal: void serve(ServerSocket serverSocket) throws IOException, InterruptedException { try (var scope = new StructuredTaskScope<Void>()) { try { while (true) { var socket = serverSocket.accept(); scope.fork(() -> handle(socket)); } } finally { // If there's been an error or we're interrupted, we stop accepting scope.shutdown(); // Close all active connections scope.join(); } } } It's not exactly clear the exact specifics of how the above works regarding child tasks being able or unable to shutdown the parent scope - it only has the exception handler for the loop itself, which then performs .shutdown, which interrupts all the child tasks. However, if we continue with this assumption, then C# implementation is just the following: async Task Serve(Socket socket) { using var cts = new CancellationTokenSource(); try { while (true) { var conn = await socket.AcceptAsync(cts.Token); // Or pass cts to Handle itself if you want to allow the handler to cancel the listener task. _ = Task.Run(() => Handle(conn, cts.Token)); } } finally { cts.Cancel(); } } With that said, the difference to Java Structured Concurrency as proposed is that it offers tree-like tracking of child tasks/green threads, control over their execution, and, possibly, nicer debugging experience. I don't have an opinion whether the semantics of .shutdown() which ends up calling .interrupt() are sound without reading more carefully. If it can interrupt the execution at any point, this is extremely dangerous and will break a lot of existing code. It's one of the reasons why thread.Abort() got deprecated in .NET - the API brings too many issues and cannot be made to work reliably without hindering optimizations or violating expectations. If it's not the case and it's more like flowing CancellationToken implicitly, then it may be a nicer experience depending on the situation. If it only interrupts upon suspending on .Join, IO wait or similar - it's still the kind of API that you are forced into instead of deciding whether cooperative cancellation is even safely possible given specific logic, without having to write try-catch blocks for each callsite. This is my surface impression and I will continue looking into this API.