{"id":18011824,"url":"https://github.com/vsilaev/tascalate-async-await","last_synced_at":"2025-08-20T06:33:00.090Z","repository":{"id":41420153,"uuid":"46503050","full_name":"vsilaev/tascalate-async-await","owner":"vsilaev","description":"Async / Await asynchronous programming model for Java versions 1.8 - 23; similar to the functionality available in C# 5. The implementation is based on continuations for Java (see my other projects).","archived":false,"fork":false,"pushed_at":"2024-05-18T08:02:53.000Z","size":839,"stargazers_count":104,"open_issues_count":3,"forks_count":12,"subscribers_count":9,"default_branch":"master","last_synced_at":"2024-12-05T18:23:06.878Z","etag":null,"topics":["async","async-await","asynchronous-programming","concurrent-programming","continuations","coroutines","java"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-2-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vsilaev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-11-19T15:55:36.000Z","updated_at":"2024-10-27T13:44:50.000Z","dependencies_parsed_at":"2024-04-20T11:37:07.863Z","dependency_job_id":"4f1c7b59-dee0-456f-bb48-6a300041ac1d","html_url":"https://github.com/vsilaev/tascalate-async-await","commit_stats":{"total_commits":223,"total_committers":3,"mean_commits":74.33333333333333,"dds":0.008968609865470878,"last_synced_commit":"6edb743d70ce909cd7b25273aa00577f55e20d9d"},"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vsilaev%2Ftascalate-async-await","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vsilaev%2Ftascalate-async-await/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vsilaev%2Ftascalate-async-await/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vsilaev%2Ftascalate-async-await/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vsilaev","download_url":"https://codeload.github.com/vsilaev/tascalate-async-await/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230400616,"owners_count":18219831,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","async-await","asynchronous-programming","concurrent-programming","continuations","coroutines","java"],"created_at":"2024-10-30T03:13:21.751Z","updated_at":"2025-08-20T06:33:00.069Z","avatar_url":"https://github.com/vsilaev.png","language":"Java","readme":"[![Maven Central](https://img.shields.io/maven-central/v/net.tascalate.async/net.tascalate.async.parent.svg)](https://search.maven.org/artifact/net.tascalate.async/net.tascalate.async.parent/1.2.8/pom) [![GitHub release](https://img.shields.io/github/release/vsilaev/tascalate-async-await.svg)](https://github.com/vsilaev/tascalate-async-await/releases/tag/1.2.8) [![license](https://img.shields.io/github/license/vsilaev/tascalate-async-await.svg)](https://github.com/vsilaev/tascalate-async-await/blob/master/LICENSE)\n# Why async-await?\nAsynchronous programming has long been a useful way to perform operations that don’t necessarily need to hold up the flow or responsiveness of an application. Generally, these are either compute-bound operations or I/O bound operations. Compute-bound operations are those where computations can be done on a separate thread, leaving the main thread to continue its own processing, while I/O bound operations involve work that takes place externally and may not need to block a thread while such work takes place. Common examples of I/O bound operations are file and network operations. \n\nTraditional asynchronous programming involves using callbacks these are executed on operation completion. The API differs across languages and libraries, but the idea is always the same: you are firing some asynchronous operation, get back some kind of `Promise` as a result and attach success/failure calbacks that are executed once asynchronous operation is completed. However, these approach is associated numerous hardships:\n1. You should explicitly pass contextual variables to callbacks. Sure, you can use lambdas to capture lexical context, but this does not eliminate the problem completly. And sometimes even sacrifies readability of the code - when you have a lot of lambda functions with complex body.\n2. Coordination of asynchronous operations with callbacks is dificult: any branching logic inside the chain of asynchronous callbacks is a pain; resource management provided by `try-with-resources` constructs are not possible with asynchronous callbacks as well as many other control flow statements; handling failures is radically different from the familiar `try/catch` used in synchronous code.\n3. Different callbacks are executed on different threads. Hence special care should be taken where the application flow resumes. The issue is very critical when application runs in managed environment like JEE or UI framework (JavaFX, Swing, etc).\n\nTo alleviate aforementioned readability and maintainability issues some languages provides `async/await` asynchronous programming model. This lets developer make asynchronous calls just as easily as she can invoke synchronous ones, with the tiny addition of a keyword `await` and without sacrifying any of asynchronous programming benefits. With `await` keyword asynchronous calls may be used inside regular control flow statements (including exception handling) as naturally as calls to synchronous methods. The list of the languages that support this model is steadly growing: C# 5, ECMAScript 7, Kotlin, Scala. \n\nTascalate Async/Await library enables `async/await` model for projects built with the Java 8 and beyond. The implementation is based on [continuations for Java](https://github.com/vsilaev/tascalate-javaflow) and provides runtime API + bytecode enchancement tools to let developers use syntax constructs similar to C# 5 or ECMAScript 2017/2018 with pure Java.\n\n# How to use ?\n## ...with Maven\nFirst, add Maven dependency to the library runtime:\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003enet.tascalate.async\u003c/groupId\u003e\n    \u003cartifactId\u003enet.tascalate.async.runtime\u003c/artifactId\u003e\n    \u003cversion\u003e1.2.8\u003c/version\u003e\n\u003c/dependency\u003e\n```\nSecond, add the following build plugins in the specified order:\n```xml\n\u003cbuild\u003e\n  \u003cplugins\u003e\n\t  \n    \u003cplugin\u003e\n      \u003cgroupId\u003enet.tascalate.async\u003c/groupId\u003e\n      \u003cartifactId\u003enet.tascalate.async.tools.maven\u003c/artifactId\u003e\n      \u003cversion\u003e1.2.8\u003c/version\u003e\n      \u003cexecutions\u003e\n        \u003cexecution\u003e\n          \u003cid\u003etascalate-async-enhance-main-classes\u003c/id\u003e \n          \u003cphase\u003eprocess-classes\u003c/phase\u003e\n          \u003cgoals\u003e\n            \u003cgoal\u003etascalate-async-enhance\u003c/goal\u003e\n          \u003c/goals\u003e\n        \u003c/execution\u003e\n\t\u003c!-- Only if you need to enhance test classes --\u003e      \n        \u003cexecution\u003e\n          \u003cid\u003etascalate-async-enhance-test-classes\u003c/id\u003e \n          \u003cphase\u003eprocess-test-classes\u003c/phase\u003e\n          \u003cgoals\u003e\n            \u003cgoal\u003etascalate-async-enhance\u003c/goal\u003e\n          \u003c/goals\u003e\n        \u003c/execution\u003e\n      \u003c/executions\u003e\n    \u003c/plugin\u003e\n\t  \n    \u003cplugin\u003e\n      \u003cgroupId\u003enet.tascalate.javaflow\u003c/groupId\u003e\n      \u003cartifactId\u003enet.tascalate.javaflow.tools.maven\u003c/artifactId\u003e\n      \u003cversion\u003e2.7.8\u003c/version\u003e\n      \u003cexecutions\u003e\n        \u003cexecution\u003e\n          \u003cid\u003ejavaflow-enhance-main-classes\u003c/id\u003e \n          \u003cphase\u003eprocess-classes\u003c/phase\u003e\n          \u003cgoals\u003e\n            \u003cgoal\u003ejavaflow-enhance\u003c/goal\u003e\n          \u003c/goals\u003e\n        \u003c/execution\u003e\n        \u003c!-- Only if you need to enhance test classes --\u003e\t\t\n        \u003cexecution\u003e\n          \u003cid\u003ejavaflow-enhance-test-classes\u003c/id\u003e \n          \u003cphase\u003eprocess-test-classes\u003c/phase\u003e\n          \u003cgoals\u003e\n            \u003cgoal\u003ejavaflow-enhance\u003c/goal\u003e\n          \u003c/goals\u003e\n        \u003c/execution\u003e\n      \u003c/executions\u003e\n    \u003c/plugin\u003e\n\t  \n  \u003c/plugins\u003e\n\u003c/build\u003e\n```\nYou are ready to start coding!\n## ...with Gradle\nAs with Maven, you have to specify both build plugins and runtime dependencies. The minimal Gradle scipt should have the following prologue:\n```groovy\nbuildscript {\n    repositories {\n        mavenCentral()\n    }\n\n    dependencies {\n        classpath 'net.tascalate.async:net.tascalate.async.tools.gradle:1.2.8'\n        classpath 'net.tascalate.javaflow:net.tascalate.javaflow.tools.gradle:2.7.8'\n        /* other plugins */\n    }\n}\n\napply plugin: \"java\"\n/* ORDER IS IMPORTANT: Async/Await before Continuations! */\napply plugin: \"async-await\"\napply plugin: \"continuations\"\n\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n    implementation 'net.tascalate.async:net.tascalate.async.runtime:1.2.8'\n    /* other dependencies */\n}\n```\nThe more advanced example with `Async/Await Extras` module + [Tascalate Concurrent](https://github.com/vsilaev/tascalate-concurrent) and `Async/Await SchedulerResolver-s` (discussed below) will be:\n```groovy\nbuildscript {\n    repositories {\n        mavenCentral()\n    }\n\n    dependencies {\n        classpath 'net.tascalate.async:net.tascalate.async.tools.gradle:1.2.8'\n        classpath 'net.tascalate.javaflow:net.tascalate.javaflow.tools.gradle:2.7.8'\n        /* other plugins */\n    }\n}\n\napply plugin: \"java\"\n/* ORDER IS IMPORTANT: Async/Await before Continuations! */\napply plugin: \"async-await\"\napply plugin: \"continuations\"\n\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n    implementation 'net.tascalate.async:net.tascalate.async.runtime:1.2.8'\n    \n    /* Async/Await Extras */\n    implementation 'net.tascalate.async:net.tascalate.async.extras:1.2.8'\n    \n    /* Promise\u003cT\u003e implementation */\n    /* Necessary because net.tascalate.async.extras uses it as an */\n    /* 'optional' dependency to avoid concrete version lock-in.   */\n    implementation 'net.tascalate:net.tascalate.concurrent:0.9.9'\n    \n    /* Necessary only for different providers */\n    runtimeOnly 'net.tascalate.async:net.tascalate.async.resolver.provided:1.2.8'\n    /*\n    runtimeOnly 'net.tascalate.async:net.tascalate.async.resolver.propagated:1.2.8'\n    */\n\n    \n    /* other dependencies */\n}\n/* Optional config */\n'async-await' {\n    /* ... */\n}\n\n'continuations' {\n    /* ... */\n}\n```\n\n# Asynchronous tasks\nThe first type of functions the library supports is asycnhronous task. Asynchronous task is a method (either instance or class method) that is annotated with `net.tascalate.async.async` annotation and returns `CompletionStage\u003cT\u003e` or `void`. In the later case it is a \"fire-and-forget\" task that is intended primarly to be used for event handlers inside UI framework (like JavaFX or Swing). Let us write a simple example:\n```java\nimport static net.tascalate.async.CallСontext.async;\nimport static net.tascalate.async.CallСontext.await;\nimport net.tascalate.async.async;\n\nimport java.util.concurrent.CompletionStage;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\n\nclass MyClass {\n    public @async CompletionStage\u003cString\u003e mergeStrings() {\n        StringBuilder result = new StringBuilder();\n        for (int i = 1; i \u003c= 10; i++) {\n            String v = await( decorateStrings(i, \"async \", \" awaited\") );\n            result.append(v).append('\\n');\n        }\n        return async(result.toString());\n    }\n    \n    public @async CompletionStage\u003cString\u003e decorateStrings(int i, String prefix, String suffix) {\n        String value = prefix + await( produceString(\"value \" + i) ) + suffix;\n        return async(value);\n    }\n    \n    // Emulate some asynchronous business service call\n    private static CompletionStage\u003cString\u003e produceString(String value) {\n        return CompletableFuture.supplyAsync(() -\u003e value, executor);\n    }\n    \n    private static final ExecutorService executor = Executors.newFixedThreadPool(4);\n}\n```\nThanks to statically imported methods of `net.tascalate.async.CallСontext` the code looks very close to the one developed with languages having native support for async/await. Both `mergeStrings` and `decorateStrings` are asynchronous methods -- they are marked with `net.tascalate.async.async` annotation and returns `CompletionStage\u003cT\u003e`. Inside these methods you may call `await` to suspend the method till the `CompletionStage\u003cT\u003e` supplied as the argument is resolved (either sucessfully or exceptionally). Please notice, that you can await for any `CompletionStage\u003cT\u003e` implementation obtained from different libraries - like inside the `decorateStrings` method, including pending result of another asynchronous method - like in `mergeStrings`. \n\nThe list of the supported return types for the async methods is:\n1. `void`\n2. `java.util.concurrent.CompletionStage`\n3. `java.util.concurrent.CompletableFuture`\n4. `net.tascalate.concurrent.Promise` (see my other project [Tascalate Concurrent](https://github.com/vsilaev/tascalate-concurrent))\n\nFor non-void results the actual result type class also implements `java.util.concurrent.Future` (even for the case [2] with `CompletionStage`). This means that you can safely upcast the result promise to the `java.util.concurrent.Future` and use blocking methods if necessary. Most importantly, you can use the `cancel(...)` method cancel the future returned.\n\nTo return a result from the asynchronous method you have to use syntactic construct `return async(value)`. You must always treat both of these statements (calling `async` method and `return`-ing its result) as the single syntactic construct and don't call `async` method separately or store it return value to variable while these will lead to unpredicatble results. It's especially important if your method body is not linear. Depending on your established coding practice how to deal with multiple returns you should use either...\n```java\npublic @async CompletionStage\u003cString\u003e foo(int i) {\n    switch (i) {\n        case 1: return async(\"A\");\n        case 2: return async(\"B\");\n        case 3: return async(\"C\");\n        default:\n            return async(\"\u003cUNKNOWN\u003e\");\n    }\n}\n```\n...or...\n```java\npublic @async CompletionStage\u003cString\u003e bar(int i) {\n    String result;\n    switch (i) {\n        case 1: result = \"A\"; break;\n        case 2: result = \"B\"; break;\n        case 3: result = \"C\"; break;\n        default:\n            result = \"\u003cUNKNOWN\u003e\";\n    }\n    return async(result);\n}\n```\nIt's worth to mention, that when developing code with async/await you should avoid so-called [\"async/await hell\"](https://medium.com/@7genblogger/escaping-so-called-async-await-hell-in-node-js-b5f5ba5fa9ca). In short, pay special attention what parts of your code may be executed in parallel and what parts require serial execution. Consider the following example:\n```java\npublic @async CompletionStage\u003cLong\u003e calculateTotalPrice(Order order) {\n    Long rawItemsPrice = await( calculateRawItemsPrice(order) );  \n    Long shippingCost  = await( calculateShippingCost(order) );  \n    Long taxes         = await( calculateTaxes(order) );  \n    return async(rawItemsPrice + shippingCost + taxes);\n}\n\nprotected @async CompletionStage\u003cLong\u003e calculateRawItemsPrice(Order order) {\n    ...\n}\n\nprotected @async CompletionStage\u003cLong\u003e calculateShippingCost(Order order) {\n    ...\n}\n\nprotected @async CompletionStage\u003cLong\u003e calculateTaxes(Order order) {\n    ...\n}\n```\nIn the above example all async methods `calculateRawItemsPrice`, `calculateShippingCost`, `calculateTaxes` are executed serially, one by one, hence the performance is degraded comparing to the following parallelized solution:\n```java\npublic @async CompletionStage\u003cLong\u003e calculateTotalPrice(Order order) {\n    CompletionStage\u003cLong\u003e rawItemsPrice = calculateRawItemsPrice(order);  \n    CompletionStage\u003cLong\u003e shippingCost  = calculateShippingCost(order);  \n    CompletionStage\u003cLong\u003e taxes         = calculateTaxes(order);  \n    return async( await(rawItemsPrice) + await(shippingCost) + await(taxes) );\n}\n```\nThis way all inner async operations are started (almost) simualtenously and are running in parallel, unlike in the first example.\n\n# Suspendable methods\nSometimes it is necessary to await for asynchronous result in some helper method that per se should not be asynchronous. To support this use case Tascalate Async/Await provides `@suspendable` annotation. The original example above hence can be rewritten as following:\n```java\nimport static net.tascalate.async.CallСontext.async;\nimport static net.tascalate.async.CallСontext.await;\nimport net.tascalate.async.async;\nimport net.tascalate.async.suspendable; // NEW ANNOTATION IMPORT\n\nimport java.util.concurrent.CompletionStage;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\n\nclass MyClass {\n    public @async CompletionStage\u003cString\u003e mergeStrings() {\n        StringBuilder result = new StringBuilder();\n        for (int i = 1; i \u003c= 10; i++) {\n\t    // No await here -- moved to helper method\n            String v = decorateStrings(i, \"async \", \" awaited\"); \n            result.append(v).append('\\n');\n        }\n        return async(result.toString());\n    }\n    \n    // Async method refactored to suspendable\n    private @suspendable String decorateStrings(int i, String prefix, String suffix) {\n        String value = prefix + await( produceString(\"value \" + i) ) + suffix;\n        return value; // Just regular \"return \u003cvalue\u003e\" instead of \"return async(\u003cvalue\u003e)\"\n    }\n    \n    // Emulate some asynchronous business service call\n    private static CompletionStage\u003cString\u003e produceString(String value) {\n        return CompletableFuture.supplyAsync(() -\u003e value, executor);\n    }\n    \n    private static final ExecutorService executor = Executors.newFixedThreadPool(4);\n}\n```\nAs you see, suspendable methods are just like regular ones but with special annotation - `@suspendable`. You should follow regular rules about returning results from this methods, moreover - it's an error to call `return async(\u003cvalue\u003e)` inside these methods. The important thing about `@suspendable` methods is that they may be called only from `@async` methods or from other `@suspendable` methods.\n\nPerformance-wise suspendable methods behaves the same as asynchronous task methods, so the question \"which kind should be used\" is justy a matter of orginizing and structuring your code . The recommended approach is to use asynchronous task methods when they are exposed to outside clients and suspendable ones for internal implementation details. However, the final decision is up to library user till s/he holds the rule that suspendable methods may be called only from asynchronous context (`@async` methods or other `@suspendable` methods) as stated above.\n\nImplemenation notes: technically suspendable methods are implemented as continuable methods that follow rules defined by [Tascalate JavaFlow](https://github.com/vsilaev/tascalate-javaflow) library, so you may use any continuable annotation that is supported by Tascalate JavaFlow, not only `@suspendable`.\n\n# Generators\nTDB\n\n# Scheduler \u0026 SchedulerResolver - where is my code executed?\n## Introducing schedulers\nWhen executing asynchronous code with `CompletionStage` / `CompletableFuture` it's critical to know where the code is resumed once the corresponding completion stage is settled. With regular `CompletionStage` API the answer is pretty straightforward: the code will be resumed with the `Executor` supplied as an additional parameter to the API method like below:\n```java\nimport java.util.concurrent.CompletionStage;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\n...\n\nCompletionStage\u003cString\u003e myCompletionStage = ... ; // Start asynchronous operation\nExecutorService myExecutor = Executors.newFixedThreadPool(4);\nmyCompletionStage.thenAcceptAsync(System.out::println, myExecutor);\n``` \nIn the example above the code to print result to the console will run on the thread provided by `myExecutor`.\n\nHowever, for Tascalate Async / Await there is no way to specify explicitly where the code will be resumed once the corresponding `await(future)` operation is complete. Instead of the passing `Executor` explicitly, the library uses more declarative pluggable mechanism to  specify asynchronous executor to run with.\n\nFirst we must introduce the `Scheduler` interface:\n```java\npackage net.tascalate.async;\n\npublic interface Scheduler {\n\n   ...    \n    \n    default Runnable contextualize(Runnable resumeContinuation) {\n        return resumeContinuation;\n    }\n    \n    abstract public CompletionStage\u003c?\u003e schedule(Runnable runnable);\n   ...\n}\n```\nThe `Schedulre` API has 2 responsibilities:\n1. Execute supplied runnable command, most probably asynchronously (thought, this is implementation-dependent)\n2. Capture execution context of the currently running thread before suspension so it can later be restored when the code is resumed after `await(future)`. The `execution context` is typically defined as a set of thread local variables - either via explicit usage of the `ThreadLocal` or via some API wrapping `ThreadLocal`-s (like [SimpAttributesContextHolder](https://docs.spring.io/spring-framework/docs/5.1.4.RELEASE_to_5.1.5.RELEASE/Spring%20Framework%205.1.5.RELEASE/org/springframework/messaging/simp/SimpAttributesContextHolder.html) in Spring) \n\nThere are several factory methods in `Scheduler` interface that create concrete `Scheduler` implementation using the `ExecutorService` supplied and optional `contextualizer` - a function that captures current thread execution context and creates a wrapper for the runnable to re-apply context on the new thread.\n\n```\npackage net.tascalate.async;\n...\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.ExecutorService;\nimport java.util.function.Function;\n...\n\npublic interface Scheduler {\n    ...\n\n    public static Scheduler nonInterruptible(Executor executor);\n    \n    public static Scheduler nonInterruptible(\n        Executor executor, \n        Function\u003c? super Runnable, ? extends Runnable\u003e contextualizer);     \n\n    public static Scheduler interruptible(ExecutorService executor);\n    \n    public static Scheduler interruptible(\n        ExecutorService executor, \n        Function\u003c? super Runnable, ? extends Runnable\u003e contextualizer);\n\n   ...\n}\n```\n\nSo the sequence to create `Scheduler` is:\n```java\nimport java.util.concurrent.CompletionStage;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\n\nimport java.util.function.Function;\n\nimport net.tascalate.async.Scheduler \n...\nExecutorService myExecutor = Executors.newFixedThreadPool(4); // Or whatever ExecutorService impl. you need\nScheduler myScheduler = Scheduler.interruptible(myExecutor, Function.identity());\n```\n\nThe careful reader may notice that there is a divide between `interruptible` vs `non-interruptible` schedulers, but let us left this out of the scope for a while. Instead, let's discuss how to apply the scheduler created to the asynchronous methods.\n\n ## Explicit (method-scoped) schedulers\n The most explicit and straightforward way to specify asynchronous `Scheduler` is to pass it explicitly to the  asynchronous method as an annotated parameter:\n```java\nimport static net.tascalate.async.CallСontext.async;\nimport static net.tascalate.async.CallСontext.await;\nimport net.tascalate.async.async;\nimport net.tascalate.async.Scheduler;\nimport net.tascalate.async.SchedulerProvider; \n\nimport java.util.concurrent.CompletionStage;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\n\npublic class MyClass {\n   public static void main(String[] argv) {\n       ExecutorService myExecutor = Executors.newFixedThreadPool(4);\n       Scheduler myScheduler = Scheduler.interruptible(myExecutor);\n       CompletionStage\u003cString\u003e myPromise = new MyClass().mergeStrings(myScheduler);\n   }\n\n    public @async CompletionStage\u003cString\u003e mergeStrings(@SchedulerProvider currentScheduler) {\n        StringBuilder result = new StringBuilder();\n        for (int i = 1; i \u003c= 10; i++) {\n            String v = await( decorateStrings(i, \"async \", \" awaited\", currentScheduler) );\n            result.append(v).append('\\n');\n        }\n        return async(result.toString());\n    }\n    \n    public @async CompletionStage\u003cString\u003e decorateStrings(int i, String prefix, String suffix, \n        @SchedulerProvider currentScheduler) {\n\n        String value = prefix + await( produceString(\"value \" + i) ) + suffix;\n        return async(value);\n    }\n    \n    // Emulate some asynchronous business service call\n    private static CompletionStage\u003cString\u003e produceString(String value) {\n        ...\n    }\n}\n```\nThe scenario is simple: add parameter of the type `Scheduler` annotated with `@SchedulerProvider` annotation to the each asynchronous method where this scheduler is used and pass it explicitly. Note that it's an error to have more than one parameter annotated with `@SchedulerProvider` - only one is allowed. Also, passing non-annotated `Scheduler` will have no effect -- it's treated just like regular parameter and do not used for asynchronous execution.\n\nYou may notice that `currentScheduler` parameter from the `mergeStrings` method is passed directly to the `decorateStrings` method. This is mandatory if you want to share the same scheduler accross several asynchronous methods. By default schedulers are not inherited for nested calls.\n\nNotice that in both methods above `currentScheduler` is not used with `await(...)` operator - it's used implicitly behinds the scenes in the generated code. This has one important implication: you can use only one scheduler per asynchronous method, there is no way to use different schedulers for different `await(...)` operations within the same method. If you ever need to then please re-factor your code to use separate asynchronous methods where individual schedulers may be defined per-method.\n\n## SchedulerResolver introduction -- propagating scheduler to nested calls\nAs it was mentioned right above \"by default schedulers are not inherited for nested calls\". This is a good point to introduce pluggable scheduler providers mechanism and alleviate this limitation.\n\nTo use pluggable schedule provider you need to add corresponding Maven dependency and introduce new artifact on the project class-path / module-path. For example:\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003enet.tascalate.async\u003c/groupId\u003e\n    \u003cartifactId\u003enet.tascalate.async.resolver.propagated\u003c/artifactId\u003e\n    \u003cversion\u003e${actual-tascalate-async-await-version}\u003c/version\u003e\n    \u003cscope\u003eruntime\u003c/scope\u003e\n\u003c/dependency\u003e\n```\nNow let us rewrite the example above to automatically propagate scheduler of the outer asynchronous method to the inner one:\n```java\nimport static net.tascalate.async.CallСontext.async;\nimport static net.tascalate.async.CallСontext.await;\nimport net.tascalate.async.async;\nimport net.tascalate.async.Scheduler;\nimport net.tascalate.async.SchedulerProvider; \nimport net.tascalate.async.spi.CurrentCallContext;\n\nimport java.util.concurrent.CompletionStage;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\n\npublic class MyClass {\n   public static void main(String[] argv) {\n       ExecutorService myExecutor = Executors.newFixedThreadPool(4);\n       Scheduler myScheduler = Scheduler.interruptible(myExecutor);\n       CompletionStage\u003cString\u003e myPromise = new MyClass().mergeStrings(myScheduler);\n   }\n\n    public @async CompletionStage\u003cString\u003e mergeStrings(@SchedulerProvider Scheduler currentScheduler) {\n        System.out.println(\"Current scheduler (outer) - \" + CurrentCallContext.scheduler());\n        StringBuilder result = new StringBuilder();\n        for (int i = 1; i \u003c= 10; i++) {\n            String v = await( decorateStrings(i, \"async \", \" awaited\") );\n            result.append(v).append('\\n');\n        }\n        return async(result.toString());\n    }\n    \n    public @async CompletionStage\u003cString\u003e decorateStrings(int i, String prefix, String suffix) {\n        System.out.println(\"Current scheduler (inner) - \" + CurrentCallContext.scheduler());\n        String value = prefix + await( produceString(\"value \" + i) ) + suffix;\n        return async(value);\n    }\n    \n    // Emulate some asynchronous business service call\n    private static CompletionStage\u003cString\u003e produceString(String value) {\n        ...\n    }\n}\n```\nYou may see that the code was simplified, however no new specific code for the \"propagating provider\" was introduced. If you run this code you will see that `CurrentCallContext.scheduler()` reports the very same scheduler for both inner and outer methods. Btw, `CurrentCallContext.scheduler()` may be used with any combination of the scheduler providers and reports currently used `Scheduler` for all asynchronous, suspendable and generators methods.\n\n## More useful SchedulerResolver - per-class/per-instance schedulers\nThe next and probably the most useful one `Scheduler` provider is a \"provided\" provider variant (no pun). The idea is that a `Scheduler` may be specified per class or per class instances as a filed  (an instance one or a static one) or as a getter-like method (no argument method with a `Scheduler` return type).\n\nTo use this provider you first need to add a new runtime dependency:\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003enet.tascalate.async\u003c/groupId\u003e\n    \u003cartifactId\u003enet.tascalate.async.resolver.provided\u003c/artifactId\u003e\n    \u003cversion\u003e${actual-tascalate-async-await-version}\u003c/version\u003e\n    \u003cscope\u003eruntime\u003c/scope\u003e\n\u003c/dependency\u003e \n```\n\nLet us modify an example from the above to use the new provider:\n```java\nimport static net.tascalate.async.CallСontext.async;\nimport static net.tascalate.async.CallСontext.await;\nimport net.tascalate.async.async;\nimport net.tascalate.async.Scheduler;\nimport net.tascalate.async.SchedulerProvider; \nimport net.tascalate.async.spi.CurrentCallContext;\n\nimport java.util.concurrent.CompletionStage;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\n\npublic class MyClass {\n   public static void main(String[] argv) {\n       ExecutorService myExecutor = Executors.newFixedThreadPool(4);\n       Scheduler myScheduler = Scheduler.interruptible(myExecutor);\n       CompletionStage\u003cString\u003e myPromise = new MyClass(myScheduler).mergeStrings();\n   }\n\n   @SchedulerProvider /* Mandatory annotation */\n   private final Scheduler scheduler;\n\n   MyClass(Scheduler scheduler) {\n     this.scheduler = scheduler;\n   }\n\n    public @async CompletionStage\u003cString\u003e mergeStrings( ) {\n        System.out.println(\"Current scheduler (outer) - \" + CurrentCallContext.scheduler());\n        StringBuilder result = new StringBuilder();\n        for (int i = 1; i \u003c= 10; i++) {\n            String v = await( decorateStrings(i, \"async \", \" awaited\") );\n            result.append(v).append('\\n');\n        }\n        return async(result.toString());\n    }\n    \n    public @async CompletionStage\u003cString\u003e decorateStrings(int i, String prefix, String suffix) {\n        System.out.println(\"Current scheduler (inner) - \" + CurrentCallContext.scheduler());\n        String value = prefix + await( produceString(\"value \" + i) ) + suffix;\n        return async(value);\n    }\n    \n    // Emulate some asynchronous business service call\n    private static CompletionStage\u003cString\u003e produceString(String value) {\n        ...\n    }\n}\n```\n\nThe `Scheduler` is provided as an instance field for all of `@async` instance methods of the class `MyClass`. You can initialize this variable in the constructor (as above) or at any time before invoking the `@async` method. In Spring / CDI environment the `scheduler` field might be injected by the container via corresponding annotation (`@Autowired` or `@Injected`). \n\nPlease notice that when you are re-assigning the field during execution of the `@async` method it has no effect on the methods these are in progress -- only freshly invoked ones will see the change. However, special consideration should be taken on account: in the example above if you re-define the `scheduler` field after `mergeStrings` invocation but before `decorateStrings` invocation then methods will use different schedulers. Also, no special synchronization is performed by the library itself, and it's library's user responsibility to synchronize access to such fields.\n\nAs it was mentioned, you can use a getter-like method annotated with `@SchedulerProvider ` to supply scheduler. Use this option when you need different schedulers for the different object states, but, again, provide all necessary state synchronization on your own.\n\nIt's an error to provide a `Scheduler` with both a field and a method, or to have more than one filed or more than one getter-like method annotated with `@SchedulerProvider `.\n\nIt was mentioned that you can use both instance and static (class) field / method to provide a `Scheduler`. However, consider the following rules:\n1. Instance-level provider supplies a `Scheduler` only to `@async` instance methods.\n2. Class-level provider supplies a `Scheduler` to static `@async` methods AND to instance methods UNLESS there is a separate instance-level provider.\n3. It's an error to have more than one class-level provider in the same class via static field(s) / static getter-like method(s) / the combination of thereof (same as with instance-level providers); however it's a fully supported scenario when you have both instance-level provider AND class-level provider: instance level provider will take precedence over the class-level provider for the `@async` instance methods.\n\nLast but not least is a visibility of the `Scheduler` provider (field / getter-like method) inherited from the superclass. It follows the same visibility rules as for the regular fields / methods inheritance: public and protected are always visible; package private are visible when both classes are in the same package; and private members are not visible. Take this on account when runtime will report you about ambiguity of the `Scheduler` provider - most probably, your subclass inherits ones from the superclasses chain.\n\n## Scoped SchedulerResolver -- overriding schedulers, providing own schedulers in DI environment\nTBD\n\n# Interruptions/cancelation of @async methods \u0026 exception handling\nTBD\n","funding_links":[],"categories":["Java","并发编程"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvsilaev%2Ftascalate-async-await","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvsilaev%2Ftascalate-async-await","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvsilaev%2Ftascalate-async-await/lists"}