diff --git a/docs/navigate/advanced-programming/toc.yml b/docs/navigate/advanced-programming/toc.yml index efb7756c4eade..5d1626a7c26d1 100644 --- a/docs/navigate/advanced-programming/toc.yml +++ b/docs/navigate/advanced-programming/toc.yml @@ -34,6 +34,8 @@ items: href: ../../standard/asynchronous-programming-patterns/common-async-bugs.md - name: Async lambda pitfalls href: ../../standard/asynchronous-programming-patterns/async-lambda-pitfalls.md + - name: Keeping async methods alive + href: ../../standard/asynchronous-programming-patterns/keeping-async-methods-alive.md - name: Event-based asynchronous pattern (EAP) items: - name: Documentation overview diff --git a/docs/standard/asynchronous-programming-patterns/common-async-bugs.md b/docs/standard/asynchronous-programming-patterns/common-async-bugs.md index 49044f9766b49..a63b8fe945976 100644 --- a/docs/standard/asynchronous-programming-patterns/common-async-bugs.md +++ b/docs/standard/asynchronous-programming-patterns/common-async-bugs.md @@ -117,5 +117,6 @@ Storing the result in a variable suppresses the warning but doesn't fix the unde - [Task-based asynchronous pattern (TAP)](task-based-asynchronous-pattern-tap.md) - [Async lambda pitfalls](async-lambda-pitfalls.md) +- [Keeping async methods alive](keeping-async-methods-alive.md) - [Asynchronous wrappers for synchronous methods](async-wrappers-for-synchronous-methods.md) - [Synchronous wrappers for asynchronous methods](synchronous-wrappers-for-asynchronous-methods.md) diff --git a/docs/standard/asynchronous-programming-patterns/consuming-the-task-based-asynchronous-pattern.md b/docs/standard/asynchronous-programming-patterns/consuming-the-task-based-asynchronous-pattern.md index 1499b3f21d020..e0b0208be96bb 100644 --- a/docs/standard/asynchronous-programming-patterns/consuming-the-task-based-asynchronous-pattern.md +++ b/docs/standard/asynchronous-programming-patterns/consuming-the-task-based-asynchronous-pattern.md @@ -1,30 +1,29 @@ --- title: "Consuming the Task-based Asynchronous Pattern" description: Learn to consume the Task-based Asynchronous Pattern (TAP) when working with asynchronous operations. -ms.date: "03/30/2017" +ms.date: "04/17/2026" helpviewer_keywords: - ".NET and TAP" - "asynchronous design patterns, .NET" - "TAP, .NET support for" - "Task-based Asynchronous Pattern, .NET support for" - ".NET, asynchronous design patterns" -ms.assetid: 033cf871-ae24-433d-8939-7a3793e547bf --- # Consuming the Task-based Asynchronous Pattern -When you use the Task-based Asynchronous Pattern (TAP) to work with asynchronous operations, you can use callbacks to achieve waiting without blocking. For tasks, this is achieved through methods such as . Language-based asynchronous support hides callbacks by allowing asynchronous operations to be awaited within normal control flow, and compiler-generated code provides this same API-level support. +When you use the Task-based Asynchronous Pattern (TAP) to work with asynchronous operations, you can use callbacks to achieve waiting without blocking. For tasks, this pattern uses methods such as . Language-based asynchronous support hides callbacks by allowing asynchronous operations to be awaited within normal control flow, and compiler-generated code provides this same API-level support. ## Suspending Execution with Await -You can use the [await](../../csharp/language-reference/operators/await.md) keyword in C# and the [Await Operator](../../visual-basic/language-reference/operators/await-operator.md) in Visual Basic to asynchronously await and objects. When you're awaiting a , the `await` expression is of type `void`. When you're awaiting a , the `await` expression is of type `TResult`. An `await` expression must occur inside the body of an asynchronous method. (These language features were introduced in .NET Framework 4.5.) +You can use the [await](../../csharp/language-reference/operators/await.md) keyword in C# and the [Await Operator](../../visual-basic/language-reference/operators/await-operator.md) in Visual Basic to asynchronously await and objects. When you await a , the `await` expression is of type `void`. When you await a , the `await` expression is of type `TResult`. An `await` expression must occur inside the body of an asynchronous method. (These language features were introduced in .NET Framework 4.5.) Under the covers, the await functionality installs a callback on the task by using a continuation. This callback resumes the asynchronous method at the point of suspension. When the asynchronous method is resumed, if the awaited operation completed successfully and was a , its `TResult` is returned. If the or that was awaited ended in the state, an exception is thrown. If the or that was awaited ended in the state, the exception that caused it to fault is thrown. A `Task` can fault as a result of multiple exceptions, but only one of these exceptions is propagated. However, the property returns an exception that contains all the errors. If a synchronization context ( object) is associated with the thread that was executing the asynchronous method at the time of suspension (for example, if the property is not `null`), the asynchronous method resumes on that same synchronization context by using the context's method. Otherwise, it relies on the task scheduler ( object) that was current at the time of suspension. Typically, this is the default task scheduler (), which targets the thread pool. This task scheduler determines whether the awaited asynchronous operation should resume where it completed or whether the resumption should be scheduled. The default scheduler typically allows the continuation to run on the thread that the awaited operation completed. - When an asynchronous method is called, it synchronously executes the body of the function up until the first await expression on an awaitable instance that has not yet completed, at which point the invocation returns to the caller. If the asynchronous method does not return `void`, a or object is returned to represent the ongoing computation. In a non-void asynchronous method, if a return statement is encountered or the end of the method body is reached, the task is completed in the final state. If an unhandled exception causes control to leave the body of the asynchronous method, the task ends in the state. If that exception is an , the task instead ends in the state. In this manner, the result or exception is eventually published. + When you call an asynchronous method, it synchronously executes the body of the function up until the first await expression on an awaitable instance that isn't yet complete, at which point the invocation returns to the caller. If the asynchronous method doesn't return `void`, it returns a or object to represent the ongoing computation. In a non-void asynchronous method, if a return statement is encountered or the end of the method body is reached, the task is completed in the final state. If an unhandled exception causes control to leave the body of the asynchronous method, the task ends in the state. If that exception is an , the task instead ends in the state. In this manner, the result or exception is eventually published. - There are several important variations of this behavior. For performance reasons, if a task has already completed by the time the task is awaited, control is not yielded, and the function continues to execute. Additionally, returning to the original context isn't always the desired behavior and can be changed; this is described in more detail in the next section. + Several important variations of this behavior exist. For performance reasons, if a task is already complete by the time the task is awaited, control isn't yielded, and the function continues to execute. Additionally, returning to the original context isn't always the desired behavior and can be changed; this behavior is described in more detail in the next section. ### Configuring Suspension and Resumption with Yield and ConfigureAwait @@ -38,30 +37,30 @@ public class Task : … } ``` - This is equivalent to asynchronously posting or scheduling back to the current context. + This method is equivalent to asynchronously posting or scheduling back to the current context. -```csharp -Task.Run(async delegate -{ - for(int i=0; i<1000000; i++) - { - await Task.Yield(); // fork the continuation into a separate work item - ... - } -}); -``` +:::code language="csharp" source="./snippets/consuming-the-task-based-asynchronous-pattern/csharp/Program.cs" id="YieldLoop"::: +:::code language="vb" source="./snippets/consuming-the-task-based-asynchronous-pattern/vb/Program.vb" id="YieldLoop"::: - You can also use the method for better control over suspension and resumption in an asynchronous method. As mentioned previously, by default, the current context is captured at the time an asynchronous method is suspended, and that captured context is used to invoke the asynchronous method's continuation upon resumption. In many cases, this is the exact behavior you want. In other cases, you may not care about the continuation context, and you can achieve better performance by avoiding such posts back to the original context. To enable this, use the method to inform the await operation not to capture and resume on the context, but to continue execution wherever the asynchronous operation that was being awaited completed: + You can also use the method for better control over suspension and resumption in an asynchronous method. As mentioned previously, by default, the current context is captured at the time an asynchronous method is suspended, and that captured context is used to invoke the asynchronous method's continuation upon resumption. In many cases, this is the exact behavior you want. In other cases, you might not care about the continuation context, and you can achieve better performance by avoiding such posts back to the original context. To enable this behavior, use the method to inform the await operation not to capture and resume on the context, but to continue execution wherever the asynchronous operation that was being awaited completed: ```csharp await someTask.ConfigureAwait(continueOnCapturedContext:false); ``` -## Canceling an Asynchronous Operation +### Awaitables, ConfigureAwait, and SynchronizationContext + +`await` works with any type that satisfies the [awaitable expression pattern](~/_csharpstandard/standard/expressions.md#12992-awaitable-expressions), not just . A type is awaitable if it provides a compatible `GetAwaiter` method that returns a type with `IsCompleted`, `OnCompleted`, and `GetResult` members. In most public APIs, return , , , or . Use custom awaitables only for specialized scenarios. + +Use when the continuation doesn't need the caller's context. In app code that updates a UI, context capture is often required. In reusable library code, `ConfigureAwait(false)` is usually preferred because it avoids unnecessary context hops and reduces deadlock risk for callers that block. + +`ConfigureAwait(false)` changes continuation scheduling, not flow. For a deeper explanation of context behavior, see [ExecutionContext and SynchronizationContext](executioncontext-synchronizationcontext.md). + +## Canceling an asynchronous operation Starting with .NET Framework 4, TAP methods that support cancellation provide at least one overload that accepts a cancellation token ( object). - A cancellation token is created through a cancellation token source ( object). The source's property returns the cancellation token that will be signaled when the source's method is called. For example, if you want to download a single webpage and you want to be able to cancel the operation, you create a object, pass its token to the TAP method, and then call the source's method when you're ready to cancel the operation: + You create a cancellation token through a cancellation token source ( object). The source's property returns the cancellation token that signals when the source's method is called. ```csharp var cts = new CancellationTokenSource(); @@ -70,7 +69,7 @@ string result = await DownloadStringTaskAsync(url, cts.Token); cts.Cancel(); ``` - To cancel multiple asynchronous invocations, you can pass the same token to all invocations: + For example, if you want to download a single webpage and you want to be able to cancel the operation, create a object, pass its token to the TAP method, and then call the source's method when you're ready to cancel the operation: ```csharp var cts = new CancellationTokenSource(); @@ -91,23 +90,23 @@ var cts = new CancellationTokenSource(); ``` > [!IMPORTANT] -> Cancellation requests may be initiated from any thread. +> Any thread can initiate cancellation requests. - You can pass the value to any method that accepts a cancellation token to indicate that cancellation will never be requested. This causes the property to return `false`, and the called method can optimize accordingly. For testing purposes, you can also pass in a pre-canceled cancellation token that is instantiated by using the constructor that accepts a Boolean value to indicate whether the token should start in an already-canceled or not-cancelable state. + You can pass the value to any method that accepts a cancellation token to indicate that cancellation is never requested. This value causes the property to return `false`, and the called method can optimize accordingly. For testing purposes, you can also pass in a pre-canceled cancellation token that is instantiated by using the constructor that accepts a Boolean value to indicate whether the token should start in an already-canceled or not-cancelable state. This approach to cancellation has several advantages: - You can pass the same cancellation token to any number of asynchronous and synchronous operations. -- The same cancellation request may be proliferated to any number of listeners. +- The same cancellation request can go to any number of listeners. -- The developer of the asynchronous API is in complete control of whether cancellation may be requested and when it may take effect. +- The developer of the asynchronous API has complete control over whether cancellation can be requested and when it takes effect. -- The code that consumes the API may selectively determine the asynchronous invocations that cancellation requests will be propagated to. +- The code that consumes the API can selectively determine the asynchronous invocations that cancellation requests go to. -## Monitoring Progress +## Monitoring progress - Some asynchronous methods expose progress through a progress interface passed into the asynchronous method. For example, consider a function that asynchronously downloads a string of text, and along the way raises progress updates that include the percentage of the download that has completed thus far. Such a method could be consumed in a Windows Presentation Foundation (WPF) application as follows: + Some asynchronous methods expose progress through a progress interface that you pass into the asynchronous method. For example, consider a function that asynchronously downloads a string of text, and along the way raises progress updates that include the percentage of the download that has completed thus far. You can consume such a method in a Windows Presentation Foundation (WPF) application as follows: ```csharp private async void btnDownload_Click(object sender, RoutedEventArgs e) @@ -124,177 +123,91 @@ private async void btnDownload_Click(object sender, RoutedEventArgs e) -## Using the Built-in Task-based Combinators +## Using the built-in task-based combinators The namespace includes several methods for composing and working with tasks. ### Task.Run - The class includes several methods that let you easily offload work as a or to the thread pool, for example: + The class includes several methods that let you easily offload work as a or to the thread pool. For example: -```csharp -public async void button1_Click(object sender, EventArgs e) -{ - textBox1.Text = await Task.Run(() => - { - // … do compute-bound work here - return answer; - }); -} -``` +:::code language="csharp" source="./snippets/consuming-the-task-based-asynchronous-pattern/csharp/Program.cs" id="TaskRunBasic"::: +:::code language="vb" source="./snippets/consuming-the-task-based-asynchronous-pattern/vb/Program.vb" id="TaskRunBasic"::: - Some of these methods, such as the overload, exist as shorthand for the method. This overload enable you to use await within the offloaded work, for example: + Some of these methods, such as the overload, exist as shorthand for the method. This overload enables you to use `await` within the offloaded work. For example: -```csharp -public async void button1_Click(object sender, EventArgs e) -{ - pictureBox1.Image = await Task.Run(async() => - { - using(Bitmap bmp1 = await DownloadFirstImageAsync()) - using(Bitmap bmp2 = await DownloadSecondImageAsync()) - return Mashup(bmp1, bmp2); - }); -} -``` +:::code language="csharp" source="./snippets/consuming-the-task-based-asynchronous-pattern/csharp/Program.cs" id="TaskRunAsync"::: +:::code language="vb" source="./snippets/consuming-the-task-based-asynchronous-pattern/vb/Program.vb" id="TaskRunAsync"::: Such overloads are logically equivalent to using the method in conjunction with the extension method in the Task Parallel Library. ### Task.FromResult - Use the method in scenarios where data may already be available and just needs to be returned from a task-returning method lifted into a : - -```csharp -public Task GetValueAsync(string key) -{ - int cachedValue; - return TryGetCachedValue(out cachedValue) ? - Task.FromResult(cachedValue) : - GetValueAsyncInternal(); -} + Use the method in scenarios where data might already be available and you just need to return it from a task-returning method lifted into a : -private async Task GetValueAsyncInternal(string key) -{ - … -} -``` +:::code language="csharp" source="./snippets/consuming-the-task-based-asynchronous-pattern/csharp/Program.cs" id="TaskFromResult"::: +:::code language="vb" source="./snippets/consuming-the-task-based-asynchronous-pattern/vb/Program.vb" id="TaskFromResult"::: ### Task.WhenAll - Use the method to asynchronously wait on multiple asynchronous operations that are represented as tasks. The method has multiple overloads that support a set of non-generic tasks or a non-uniform set of generic tasks (for example, asynchronously waiting for multiple void-returning operations, or asynchronously waiting for multiple value-returning methods where each value may have a different type) and to support a uniform set of generic tasks (such as asynchronously waiting for multiple `TResult`-returning methods). + Use the method to asynchronously wait on multiple asynchronous operations that are represented as tasks. The method has multiple overloads that support a set of non-generic tasks or a non-uniform set of generic tasks (for example, asynchronously waiting for multiple void-returning operations, or asynchronously waiting for multiple value-returning methods where each value might have a different type) and to support a uniform set of generic tasks (such as asynchronously waiting for multiple `TResult`-returning methods). - Let's say you want to send email messages to several customers. You can overlap sending the messages so you're not waiting for one message to complete before sending the next. You can also find out when the send operations have completed and whether any errors have occurred: + Suppose you want to send email messages to several customers. You can overlap sending the messages so you're not waiting for one message to complete before sending the next. You can also find out when the send operations complete and whether any errors occur: ```csharp IEnumerable asyncOps = from addr in addrs select SendMailAsync(addr); await Task.WhenAll(asyncOps); ``` - This code doesn't explicitly handle exceptions that may occur, but lets exceptions propagate out of the `await` on the resulting task from . To handle the exceptions, you can use code such as the following: + This code doesn't explicitly handle exceptions that might occur, but it lets exceptions propagate out of the `await` on the resulting task from . To handle the exceptions, use code such as the following: -```csharp -IEnumerable asyncOps = from addr in addrs select SendMailAsync(addr); -try -{ - await Task.WhenAll(asyncOps); -} -catch(Exception exc) -{ - ... -} -``` +:::code language="csharp" source="./snippets/consuming-the-task-based-asynchronous-pattern/csharp/Program.cs" id="WhenAllWithCatch"::: +:::code language="vb" source="./snippets/consuming-the-task-based-asynchronous-pattern/vb/Program.vb" id="WhenAllWithCatch"::: - In this case, if any asynchronous operation fails, all the exceptions will be consolidated in an exception, which is stored in the that is returned from the method. However, only one of those exceptions is propagated by the `await` keyword. If you want to examine all the exceptions, you can rewrite the previous code as follows: + In this case, if any asynchronous operation fails, all the exceptions are consolidated in an exception, which is stored in the that is returned from the method. However, only one of those exceptions is propagated by the `await` keyword. If you want to examine all the exceptions, you can rewrite the previous code as follows: -```csharp -Task [] asyncOps = (from addr in addrs select SendMailAsync(addr)).ToArray(); -try -{ - await Task.WhenAll(asyncOps); -} -catch(Exception exc) -{ - foreach(Task faulted in asyncOps.Where(t => t.IsFaulted)) - { - … // work with faulted and faulted.Exception - } -} -``` +:::code language="csharp" source="./snippets/consuming-the-task-based-asynchronous-pattern/csharp/Program.cs" id="WhenAllExamineExceptions"::: +:::code language="vb" source="./snippets/consuming-the-task-based-asynchronous-pattern/vb/Program.vb" id="WhenAllExamineExceptions"::: - Let's consider an example of downloading multiple files from the web asynchronously. In this case, all the asynchronous operations have homogeneous result types, and it's easy to access the results: + Consider an example of downloading multiple files from the web asynchronously. In this case, all the asynchronous operations have homogeneous result types, and it's easy to access the results: ```csharp string [] pages = await Task.WhenAll( from url in urls select DownloadStringTaskAsync(url)); ``` - You can use the same exception-handling techniques we discussed in the previous void-returning scenario: + You can use the same exception-handling techniques discussed in the previous void-returning scenario: -```csharp -Task [] asyncOps = - (from url in urls select DownloadStringTaskAsync(url)).ToArray(); -try -{ - string [] pages = await Task.WhenAll(asyncOps); - ... -} -catch(Exception exc) -{ - foreach(Task faulted in asyncOps.Where(t => t.IsFaulted)) - { - … // work with faulted and faulted.Exception - } -} -``` +:::code language="csharp" source="./snippets/consuming-the-task-based-asynchronous-pattern/csharp/Program.cs" id="WhenAllDownloadPagesExceptions"::: +:::code language="vb" source="./snippets/consuming-the-task-based-asynchronous-pattern/vb/Program.vb" id="WhenAllDownloadPagesExceptions"::: ### Task.WhenAny - You can use the method to asynchronously wait for just one of multiple asynchronous operations represented as tasks to complete. This method serves four primary use cases: + Use the method to asynchronously wait for just one of multiple asynchronous operations represented as tasks to complete. This method serves four primary use cases: -- Redundancy: Performing an operation multiple times and selecting the one that completes first (for example, contacting multiple stock quote web services that will produce a single result and selecting the one that completes the fastest). +- Redundancy: Performing an operation multiple times and selecting the one that completes first (for example, contacting multiple stock quote web services that return a single result and selecting the one that completes the fastest). - Interleaving: Launching multiple operations and waiting for all of them to complete, but processing them as they complete. -- Throttling: Allowing additional operations to begin as others complete. This is an extension of the interleaving scenario. +- Throttling: Allowing additional operations to begin as others complete. This scenario is an extension of the interleaving scenario. - Early bailout: For example, an operation represented by task t1 can be grouped in a task with another task t2, and you can wait on the task. Task t2 could represent a time-out, or cancellation, or some other signal that causes the task to complete before t1 completes. #### Redundancy - Consider a case where you want to make a decision about whether to buy a stock. There are several stock recommendation web services that you trust, but depending on daily load, each service can end up being slow at different times. You can use the method to receive a notification when any operation completes: + Consider a case where you want to make a decision about whether to buy a stock. Several stock recommendation web services exist that you trust, but depending on daily load, each service can end up being slow at different times. Use the method to receive a notification when any operation completes: -```csharp -var recommendations = new List>() -{ - GetBuyRecommendation1Async(symbol), - GetBuyRecommendation2Async(symbol), - GetBuyRecommendation3Async(symbol) -}; -Task recommendation = await Task.WhenAny(recommendations); -if (await recommendation) BuyStock(symbol); -``` +:::code language="csharp" source="./snippets/consuming-the-task-based-asynchronous-pattern/csharp/Program.cs" id="WhenAnyRedundancy"::: +:::code language="vb" source="./snippets/consuming-the-task-based-asynchronous-pattern/vb/Program.vb" id="WhenAnyRedundancy"::: Unlike , which returns the unwrapped results of all tasks that completed successfully, returns the task that completed. If a task fails, it's important to know that it failed, and if a task succeeds, it's important to know which task the return value is associated with. Therefore, you need to access the result of the returned task, or further await it, as this example shows. As with , you have to be able to accommodate exceptions. Because you receive the completed task back, you can await the returned task to have errors propagated, and `try/catch` them appropriately; for example: -```csharp -Task [] recommendations = …; -while(recommendations.Count > 0) -{ - Task recommendation = await Task.WhenAny(recommendations); - try - { - if (await recommendation) BuyStock(symbol); - break; - } - catch(WebException exc) - { - recommendations.Remove(recommendation); - } -} -``` +:::code language="csharp" source="./snippets/consuming-the-task-based-asynchronous-pattern/csharp/Program.cs" id="WhenAnyRetryOnException"::: +:::code language="vb" source="./snippets/consuming-the-task-based-asynchronous-pattern/vb/Program.vb" id="WhenAnyRetryOnException"::: - Additionally, even if a first task completes successfully, subsequent tasks may fail. At this point, you have several options for dealing with exceptions: You can wait until all the launched tasks have completed, in which case you can use the method, or you can decide that all exceptions are important and must be logged. For this, you can use continuations to receive a notification when tasks have completed asynchronously: + Additionally, even if a first task completes successfully, subsequent tasks might fail. At this point, you have several options for dealing with exceptions: You can wait until all the launched tasks complete, in which case you can use the method, or you can decide that all exceptions are important and must be logged. For this scenario, you can use continuations to receive a notification when tasks complete asynchronously: ```csharp foreach(Task recommendation in recommendations) @@ -316,232 +229,62 @@ foreach(Task recommendation in recommendations) or even: -```csharp -private static async void LogCompletionIfFailed(IEnumerable tasks) -{ - foreach(var task in tasks) - { - try { await task; } - catch(Exception exc) { Log(exc); } - } -} -… -LogCompletionIfFailed(recommendations); -``` - - Finally, you may want to cancel all the remaining operations: +:::code language="csharp" source="./snippets/consuming-the-task-based-asynchronous-pattern/csharp/Program.cs" id="LogCompletionIfFailed"::: +:::code language="vb" source="./snippets/consuming-the-task-based-asynchronous-pattern/vb/Program.vb" id="LogCompletionIfFailed"::: -```csharp -var cts = new CancellationTokenSource(); -var recommendations = new List>() -{ - GetBuyRecommendation1Async(symbol, cts.Token), - GetBuyRecommendation2Async(symbol, cts.Token), - GetBuyRecommendation3Async(symbol, cts.Token) -}; + Finally, you might want to cancel all the remaining operations: -Task recommendation = await Task.WhenAny(recommendations); -cts.Cancel(); -if (await recommendation) BuyStock(symbol); -``` +:::code language="csharp" source="./snippets/consuming-the-task-based-asynchronous-pattern/csharp/Program.cs" id="WhenAnyCancelRemainder"::: +:::code language="vb" source="./snippets/consuming-the-task-based-asynchronous-pattern/vb/Program.vb" id="WhenAnyCancelRemainder"::: #### Interleaving Consider a case where you're downloading images from the web and processing each image (for example, adding the image to a UI control). You process the images sequentially on the UI thread, but want to download the images as concurrently as possible. Also, you don't want to hold up adding the images to the UI until they're all downloaded. Instead, you want to add them as they complete. -```csharp -List> imageTasks = - (from imageUrl in urls select GetBitmapAsync(imageUrl)).ToList(); -while(imageTasks.Count > 0) -{ - try - { - Task imageTask = await Task.WhenAny(imageTasks); - imageTasks.Remove(imageTask); - - Bitmap image = await imageTask; - panel.AddImage(image); - } - catch{} -} -``` +:::code language="csharp" source="./snippets/consuming-the-task-based-asynchronous-pattern/csharp/Program.cs" id="WhenAnyInterleaving"::: +:::code language="vb" source="./snippets/consuming-the-task-based-asynchronous-pattern/vb/Program.vb" id="WhenAnyInterleaving"::: You can also apply interleaving to a scenario that involves computationally intensive processing on the of the downloaded images; for example: -```csharp -List> imageTasks = - (from imageUrl in urls select GetBitmapAsync(imageUrl) - .ContinueWith(t => ConvertImage(t.Result)).ToList(); -while(imageTasks.Count > 0) -{ - try - { - Task imageTask = await Task.WhenAny(imageTasks); - imageTasks.Remove(imageTask); - - Bitmap image = await imageTask; - panel.AddImage(image); - } - catch{} -} -``` +:::code language="csharp" source="./snippets/consuming-the-task-based-asynchronous-pattern/csharp/Program.cs" id="WhenAnyInterleavingWithProcessing"::: +:::code language="vb" source="./snippets/consuming-the-task-based-asynchronous-pattern/vb/Program.vb" id="WhenAnyInterleavingWithProcessing"::: #### Throttling - Consider the interleaving example, except that the user is downloading so many images that the downloads have to be throttled; for example, you want only a specific number of downloads to happen concurrently. To achieve this, you can start a subset of the asynchronous operations. As operations complete, you can start additional operations to take their place: - -```csharp -const int CONCURRENCY_LEVEL = 15; -Uri [] urls = …; -int nextIndex = 0; -var imageTasks = new List>(); -while(nextIndex < CONCURRENCY_LEVEL && nextIndex < urls.Length) -{ - imageTasks.Add(GetBitmapAsync(urls[nextIndex])); - nextIndex++; -} - -while(imageTasks.Count > 0) -{ - try - { - Task imageTask = await Task.WhenAny(imageTasks); - imageTasks.Remove(imageTask); - - Bitmap image = await imageTask; - panel.AddImage(image); - } - catch(Exception exc) { Log(exc); } + Consider the interleaving example, except that the user is downloading so many images that the downloads have to be throttled. For example, you want only a specific number of downloads to happen concurrently. To achieve this goal, start a subset of the asynchronous operations. As operations complete, you can start additional operations to take their place: - if (nextIndex < urls.Length) - { - imageTasks.Add(GetBitmapAsync(urls[nextIndex])); - nextIndex++; - } -} -``` +:::code language="csharp" source="./snippets/consuming-the-task-based-asynchronous-pattern/csharp/Program.cs" id="WhenAnyThrottling"::: +:::code language="vb" source="./snippets/consuming-the-task-based-asynchronous-pattern/vb/Program.vb" id="WhenAnyThrottling"::: #### Early Bailout Consider that you're waiting asynchronously for an operation to complete while simultaneously responding to a user's cancellation request (for example, the user clicked a cancel button). The following code illustrates this scenario: -```csharp -private CancellationTokenSource m_cts; - -public void btnCancel_Click(object sender, EventArgs e) -{ - if (m_cts != null) m_cts.Cancel(); -} - -public async void btnRun_Click(object sender, EventArgs e) -{ - m_cts = new CancellationTokenSource(); - btnRun.Enabled = false; - try - { - Task imageDownload = GetBitmapAsync(txtUrl.Text); - await UntilCompletionOrCancellation(imageDownload, m_cts.Token); - if (imageDownload.IsCompleted) - { - Bitmap image = await imageDownload; - panel.AddImage(image); - } - else imageDownload.ContinueWith(t => Log(t)); - } - finally { btnRun.Enabled = true; } -} - -private static async Task UntilCompletionOrCancellation( - Task asyncOp, CancellationToken ct) -{ - var tcs = new TaskCompletionSource(); - using(ct.Register(() => tcs.TrySetResult(true))) - await Task.WhenAny(asyncOp, tcs.Task); - return asyncOp; -} -``` +:::code language="csharp" source="./snippets/consuming-the-task-based-asynchronous-pattern/csharp/Program.cs" id="EarlyBailoutUI"::: +:::code language="vb" source="./snippets/consuming-the-task-based-asynchronous-pattern/vb/Program.vb" id="EarlyBailoutUI"::: This implementation re-enables the user interface as soon as you decide to bail out, but doesn't cancel the underlying asynchronous operations. Another alternative would be to cancel the pending operations when you decide to bail out, but not reestablish the user interface until the operations complete, potentially due to ending early due to the cancellation request: -```csharp -private CancellationTokenSource m_cts; - -public async void btnRun_Click(object sender, EventArgs e) -{ - m_cts = new CancellationTokenSource(); - - btnRun.Enabled = false; - try - { - Task imageDownload = GetBitmapAsync(txtUrl.Text, m_cts.Token); - await UntilCompletionOrCancellation(imageDownload, m_cts.Token); - Bitmap image = await imageDownload; - panel.AddImage(image); - } - catch(OperationCanceledException) {} - finally { btnRun.Enabled = true; } -} -``` +:::code language="csharp" source="./snippets/consuming-the-task-based-asynchronous-pattern/csharp/Program.cs" id="EarlyBailoutWithTokenUI"::: +:::code language="vb" source="./snippets/consuming-the-task-based-asynchronous-pattern/vb/Program.vb" id="EarlyBailoutWithTokenUI"::: Another example of early bailout involves using the method in conjunction with the method, as discussed in the next section. ### Task.Delay - You can use the method to introduce pauses into an asynchronous method's execution. This is useful for many kinds of functionality, including building polling loops and delaying the handling of user input for a predetermined period of time. The method can also be useful in combination with for implementing time-outs on awaits. + Use the method to add pauses into an asynchronous method's execution. This pause is useful for many kinds of functionality, including building polling loops and delaying the handling of user input for a predetermined period of time. You can also use the method with to implement time-outs on awaits. - If a task that's part of a larger asynchronous operation (for example, an ASP.NET web service) takes too long to complete, the overall operation could suffer, especially if it fails to ever complete. For this reason, it's important to be able to time out when waiting on an asynchronous operation. The synchronous , , and methods accept time-out values, but the corresponding / and the previously mentioned / methods do not. Instead, you can use and in combination to implement a time-out. + If a task that's part of a larger asynchronous operation (for example, an ASP.NET web service) takes too long to complete, the overall operation could suffer, especially if it fails to ever complete. For this reason, it's important to be able to time out when waiting on an asynchronous operation. The synchronous , , and methods accept time-out values, but the corresponding / and the previously mentioned / methods don't. Instead, use and together to implement a time-out. - For example, in your UI application, let's say that you want to download an image and disable the UI while the image is downloading. However, if the download takes too long, you want to re-enable the UI and discard the download: + For example, in your UI application, suppose that you want to download an image and disable the UI while the image is downloading. However, if the download takes too long, you want to re-enable the UI and discard the download: -```csharp -public async void btnDownload_Click(object sender, EventArgs e) -{ - btnDownload.Enabled = false; - try - { - Task download = GetBitmapAsync(url); - if (download == await Task.WhenAny(download, Task.Delay(3000))) - { - Bitmap bmp = await download; - pictureBox.Image = bmp; - status.Text = "Downloaded"; - } - else - { - pictureBox.Image = null; - status.Text = "Timed out"; - var ignored = download.ContinueWith( - t => Trace("Task finally completed")); - } - } - finally { btnDownload.Enabled = true; } -} -``` +:::code language="csharp" source="./snippets/consuming-the-task-based-asynchronous-pattern/csharp/Program.cs" id="DelayTimeout"::: +:::code language="vb" source="./snippets/consuming-the-task-based-asynchronous-pattern/vb/Program.vb" id="DelayTimeout"::: - The same applies to multiple downloads, because returns a task: + The same principle applies to multiple downloads, because returns a task: -```csharp -public async void btnDownload_Click(object sender, RoutedEventArgs e) -{ - btnDownload.Enabled = false; - try - { - Task downloads = - Task.WhenAll(from url in urls select GetBitmapAsync(url)); - if (downloads == await Task.WhenAny(downloads, Task.Delay(3000))) - { - foreach(var bmp in downloads.Result) panel.AddImage(bmp); - status.Text = "Downloaded"; - } - else - { - status.Text = "Timed out"; - downloads.ContinueWith(t => Log(t)); - } - } - finally { btnDownload.Enabled = true; } -} -``` +:::code language="csharp" source="./snippets/consuming-the-task-based-asynchronous-pattern/csharp/Program.cs" id="DelayTimeoutMultiple"::: +:::code language="vb" source="./snippets/consuming-the-task-based-asynchronous-pattern/vb/Program.vb" id="DelayTimeoutMultiple"::: ## Building Task-based Combinators @@ -549,37 +292,17 @@ public async void btnDownload_Click(object sender, RoutedEventArgs e) ### RetryOnFault - In many situations, you may want to retry an operation if a previous attempt fails. For synchronous code, you might build a helper method such as `RetryOnFault` in the following example to accomplish this: + In many situations, you want to retry an operation if a previous attempt fails. For synchronous code, you might build a helper method such as `RetryOnFault` in the following example to accomplish this task: -```csharp -public static T RetryOnFault( - Func function, int maxTries) -{ - for(int i=0; i RetryOnFault( - Func> function, int maxTries) -{ - for(int i=0; i DownloadStringTaskAsync(url), 3); ``` - You could extend the `RetryOnFault` function further. For example, the function could accept another `Func` that will be invoked between retries to determine when to try the operation again; for example: + You can extend the `RetryOnFault` function further. For example, the function could accept another `Func` that the function invokes between retries to determine when to try the operation again. For example: -```csharp -public static async Task RetryOnFault( - Func> function, int maxTries, Func retryWhen) -{ - for(int i=0; i NeedOnlyOne( - params Func> [] functions) -{ - var cts = new CancellationTokenSource(); - var tasks = (from function in functions - select function(cts.Token)).ToArray(); - var completed = await Task.WhenAny(tasks).ConfigureAwait(false); - cts.Cancel(); - foreach(var task in tasks) - { - var ignored = task.ContinueWith( - t => Log(t), TaskContinuationOptions.OnlyOnFaulted); - } - return completed; -} -``` +:::code language="csharp" source="./snippets/consuming-the-task-based-asynchronous-pattern/csharp/Program.cs" id="NeedOnlyOne"::: +:::code language="vb" source="./snippets/consuming-the-task-based-asynchronous-pattern/vb/Program.vb" id="NeedOnlyOne"::: You can then use this function as follows: @@ -643,38 +340,14 @@ double currentPrice = await NeedOnlyOne( ct => GetCurrentPriceFromServer3Async("msft", ct)); ``` -### Interleaved Operations +### Interleaved operations - There is a potential performance problem with using the method to support an interleaving scenario when you're working with large sets of tasks. Every call to results in a continuation being registered with each task. For N number of tasks, this results in O(N2) continuations created over the lifetime of the interleaving operation. If you're working with a large set of tasks, you can use a combinator (`Interleaved` in the following example) to address the performance issue: + Using the method to support an interleaving scenario can cause a performance problem when you work with large sets of tasks. Each call to registers a continuation with each task. For N number of tasks, this process creates O(N2) continuations over the lifetime of the interleaving operation. If you're working with a large set of tasks, use a combinator (`Interleaved` in the following example) to address the performance problem: -```csharp -static IEnumerable> Interleaved(IEnumerable> tasks) -{ - var inputTasks = tasks.ToList(); - var sources = (from _ in Enumerable.Range(0, inputTasks.Count) - select new TaskCompletionSource()).ToList(); - int nextTaskIndex = -1; - foreach (var inputTask in inputTasks) - { - inputTask.ContinueWith(completed => - { - var source = sources[Interlocked.Increment(ref nextTaskIndex)]; - if (completed.IsFaulted) - source.TrySetException(completed.Exception.InnerExceptions); - else if (completed.IsCanceled) - source.TrySetCanceled(); - else - source.TrySetResult(completed.Result); - }, CancellationToken.None, - TaskContinuationOptions.ExecuteSynchronously, - TaskScheduler.Default); - } - return from source in sources - select source.Task; -} -``` +:::code language="csharp" source="./snippets/consuming-the-task-based-asynchronous-pattern/csharp/Program.cs" id="Interleaved"::: +:::code language="vb" source="./snippets/consuming-the-task-based-asynchronous-pattern/vb/Program.vb" id="Interleaved"::: - You can then use the combinator to process the results of tasks as they complete; for example: + Use the combinator to process the results of tasks as they complete. For example: ```csharp IEnumerable> tasks = ...; @@ -687,62 +360,23 @@ foreach(var task in Interleaved(tasks)) ### WhenAllOrFirstException - In certain scatter/gather scenarios, you might want to wait for all tasks in a set, unless one of them faults, in which case you want to stop waiting as soon as the exception occurs. You can accomplish that with a combinator method such as `WhenAllOrFirstException` in the following example: - -```csharp -public static Task WhenAllOrFirstException(IEnumerable> tasks) -{ - var inputs = tasks.ToList(); - var ce = new CountdownEvent(inputs.Count); - var tcs = new TaskCompletionSource(); + In certain scatter/gather scenarios, you might want to wait for all tasks in a set, unless one of them faults. In that case, you want to stop waiting as soon as the exception occurs. You can accomplish that behavior by using a combinator method such as `WhenAllOrFirstException` in the following example: - Action onCompleted = (Task completed) => - { - if (completed.IsFaulted) - tcs.TrySetException(completed.Exception.InnerExceptions); - if (ce.Signal() && !tcs.Task.IsCompleted) - tcs.TrySetResult(inputs.Select(t => t.Result).ToArray()); - }; - - foreach (var t in inputs) t.ContinueWith(onCompleted); - return tcs.Task; -} -``` +:::code language="csharp" source="./snippets/consuming-the-task-based-asynchronous-pattern/csharp/Program.cs" id="WhenAllOrFirstException"::: +:::code language="vb" source="./snippets/consuming-the-task-based-asynchronous-pattern/vb/Program.vb" id="WhenAllOrFirstException"::: -## Building Task-based Data Structures +## Building task-based data structures In addition to the ability to build custom task-based combinators, having a data structure in and that represents both the results of an asynchronous operation and the necessary synchronization to join with it makes it a powerful type on which to build custom data structures to be used in asynchronous scenarios. ### AsyncCache - One important aspect of a task is that it may be handed out to multiple consumers, all of whom may await it, register continuations with it, get its result or exceptions (in the case of ), and so on. This makes and perfectly suited to be used in an asynchronous caching infrastructure. Here's an example of a small but powerful asynchronous cache built on top of : - -```csharp -public class AsyncCache -{ - private readonly Func> _valueFactory; - private readonly ConcurrentDictionary>> _map; - - public AsyncCache(Func> valueFactory) - { - if (valueFactory == null) throw new ArgumentNullException("valueFactory"); - _valueFactory = valueFactory; - _map = new ConcurrentDictionary>>(); - } + One important aspect of a task is that you can hand it out to multiple consumers. All of the consumers can await it, register continuations with it, get its result or exceptions (in the case of ), and so on. This aspect makes and perfectly suited to be used in an asynchronous caching infrastructure. Here's an example of a small but powerful asynchronous cache built on top of : - public Task this[TKey key] - { - get - { - if (key == null) throw new ArgumentNullException("key"); - return _map.GetOrAdd(key, toAdd => - new Lazy>(() => _valueFactory(toAdd))).Value; - } - } -} -``` +:::code language="csharp" source="./snippets/consuming-the-task-based-asynchronous-pattern/csharp/Program.cs" id="AsyncCache"::: +:::code language="vb" source="./snippets/consuming-the-task-based-asynchronous-pattern/vb/Program.vb" id="AsyncCache"::: - The [AsyncCache\](https://devblogs.microsoft.com/pfxteam/parallelextensionsextras-tour-12-asynccache/) class accepts as a delegate to its constructor a function that takes a `TKey` and returns a . Any previously accessed values from the cache are stored in the internal dictionary, and the `AsyncCache` ensures that only one task is generated per key, even if the cache is accessed concurrently. + The [AsyncCache\](https://devblogs.microsoft.com/pfxteam/parallelextensionsextras-tour-12-asynccache/) class accepts as a delegate to its constructor a function that takes a `TKey` and returns a . The internal dictionary stores any previously accessed values from the cache, and the `AsyncCache` ensures that it generates only one task per key, even if the cache is accessed concurrently. For example, you can build a cache for downloaded web pages: @@ -753,100 +387,27 @@ private AsyncCache m_webPages = You can then use this cache in asynchronous methods whenever you need the contents of a web page. The `AsyncCache` class ensures that you're downloading as few pages as possible, and caches the results. -```csharp -private async void btnDownload_Click(object sender, RoutedEventArgs e) -{ - btnDownload.IsEnabled = false; - try - { - txtContents.Text = await m_webPages["https://www.microsoft.com"]; - } - finally { btnDownload.IsEnabled = true; } -} -``` +:::code language="csharp" source="./snippets/consuming-the-task-based-asynchronous-pattern/csharp/Program.cs" id="AsyncCacheUsage"::: +:::code language="vb" source="./snippets/consuming-the-task-based-asynchronous-pattern/vb/Program.vb" id="AsyncCacheUsage"::: ### AsyncProducerConsumerCollection - You can also use tasks to build data structures for coordinating asynchronous activities. Consider one of the classic parallel design patterns: producer/consumer. In this pattern, producers generate data that is consumed by consumers, and the producers and consumers may run in parallel. For example, the consumer processes item 1, which was previously generated by a producer who is now producing item 2. For the producer/consumer pattern, you invariably need some data structure to store the work created by producers so that the consumers may be notified of new data and find it when available. + You can also use tasks to build data structures for coordinating asynchronous activities. Consider one of the classic parallel design patterns: producer/consumer. In this pattern, producers generate data that consumers consume, and the producers and consumers can run in parallel. For example, the consumer processes item 1, which was previously generated by a producer who is now producing item 2. For the producer/consumer pattern, you always need some data structure to store the work created by producers so that the consumers can be notified of new data and find it when available. Here's a simple data structure, built on top of tasks, that enables asynchronous methods to be used as producers and consumers: -```csharp -public class AsyncProducerConsumerCollection -{ - private readonly Queue m_collection = new Queue(); - private readonly Queue> m_waiting = - new Queue>(); - - public void Add(T item) - { - TaskCompletionSource tcs = null; - lock (m_collection) - { - if (m_waiting.Count > 0) tcs = m_waiting.Dequeue(); - else m_collection.Enqueue(item); - } - if (tcs != null) tcs.TrySetResult(item); - } - - public Task Take() - { - lock (m_collection) - { - if (m_collection.Count > 0) - { - return Task.FromResult(m_collection.Dequeue()); - } - else - { - var tcs = new TaskCompletionSource(); - m_waiting.Enqueue(tcs); - return tcs.Task; - } - } - } -} -``` +:::code language="csharp" source="./snippets/consuming-the-task-based-asynchronous-pattern/csharp/Program.cs" id="AsyncProducerConsumerCollection"::: +:::code language="vb" source="./snippets/consuming-the-task-based-asynchronous-pattern/vb/Program.vb" id="AsyncProducerConsumerCollection"::: With that data structure in place, you can write code such as the following: -```csharp -private static AsyncProducerConsumerCollection m_data = …; -… -private static async Task ConsumerAsync() -{ - while(true) - { - int nextItem = await m_data.Take(); - ProcessNextItem(nextItem); - } -} -… -private static void Produce(int data) -{ - m_data.Add(data); -} -``` +:::code language="csharp" source="./snippets/consuming-the-task-based-asynchronous-pattern/csharp/Program.cs" id="AsyncProducerConsumerUsage"::: +:::code language="vb" source="./snippets/consuming-the-task-based-asynchronous-pattern/vb/Program.vb" id="AsyncProducerConsumerUsage"::: The namespace includes the type, which you can use in a similar manner, but without having to build a custom collection type: -```csharp -private static BufferBlock m_data = …; -… -private static async Task ConsumerAsync() -{ - while(true) - { - int nextItem = await m_data.ReceiveAsync(); - ProcessNextItem(nextItem); - } -} -… -private static void Produce(int data) -{ - m_data.Post(data); -} -``` +:::code language="csharp" source="./snippets/consuming-the-task-based-asynchronous-pattern/csharp/Program.cs" id="BufferBlockUsage"::: +:::code language="vb" source="./snippets/consuming-the-task-based-asynchronous-pattern/vb/Program.vb" id="BufferBlockUsage"::: > [!NOTE] > The namespace is available as a NuGet package. To install the assembly that contains the namespace, open your project in Visual Studio, choose **Manage NuGet Packages** from the Project menu, and search online for the `System.Threading.Tasks.Dataflow` package. diff --git a/docs/standard/asynchronous-programming-patterns/implementing-the-task-based-asynchronous-pattern.md b/docs/standard/asynchronous-programming-patterns/implementing-the-task-based-asynchronous-pattern.md index 563c447bee263..a692644e530b0 100644 --- a/docs/standard/asynchronous-programming-patterns/implementing-the-task-based-asynchronous-pattern.md +++ b/docs/standard/asynchronous-programming-patterns/implementing-the-task-based-asynchronous-pattern.md @@ -1,7 +1,7 @@ --- title: "Implementing the Task-based Asynchronous Pattern" description: This article explains how to implement the Task-based Asynchronous Pattern. You can use it to implement compute-bound and I/O-bound asynchronous operations. -ms.date: "06/14/2017" +ms.date: "04/17/2026" dev_langs: - "csharp" - "vb" @@ -10,96 +10,103 @@ helpviewer_keywords: - "TAP, .NET support for" - "Task-based Asynchronous Pattern, .NET support for" - ".NET, asynchronous design patterns" -ms.assetid: fab6bd41-91bd-44ad-86f9-d8319988aa78 --- -# Implementing the Task-based Asynchronous Pattern +# Implementing the task-based asynchronous pattern -You can implement the Task-based Asynchronous Pattern (TAP) in three ways: by using the C# and Visual Basic compilers in Visual Studio, manually, or through a combination of the compiler and manual methods. The following sections discuss each method in detail. You can use the TAP pattern to implement both compute-bound and I/O-bound asynchronous operations. The [Workloads](#workloads) section discusses each type of operation. +You can implement the task-based asynchronous pattern (TAP) in three ways: by using the C# and Visual Basic compilers in Visual Studio, manually, or through a combination of the compiler and manual methods. The following sections discuss each method in detail. You can use the TAP pattern to implement both compute-bound and I/O-bound asynchronous operations. The [Workloads](#workloads) section discusses each type of operation. ## Generating TAP methods ### Using the compilers -Starting with .NET Framework 4.5, any method that is attributed with the `async` keyword (`Async` in Visual Basic) is considered an asynchronous method, and the C# and Visual Basic compilers perform the necessary transformations to implement the method asynchronously by using TAP. An asynchronous method should return either a or a object. For the latter, the body of the function should return a `TResult`, and the compiler ensures that this result is made available through the resulting task object. Similarly, any exceptions that go unhandled within the body of the method are marshalled to the output task and cause the resulting task to end in the state. The exception to this rule is when an (or derived type) goes unhandled, in which case the resulting task ends in the state. +Starting with .NET Framework 4.5, any method that is attributed with the `async` keyword (`Async` in Visual Basic) is considered an asynchronous method. The C# and Visual Basic compilers perform the necessary transformations to implement the method asynchronously by using TAP. An asynchronous method should return either a or a object. For the latter, the body of the function should return a `TResult`, and the compiler ensures that this result is made available through the resulting task object. Similarly, any exceptions that go unhandled within the body of the method are marshalled to the output task and cause the resulting task to end in the state. The exception to this rule is when an (or derived type) goes unhandled, in which case the resulting task ends in the state. + +### Task.Start and task disposal + +Use only for tasks explicitly created with a constructor that are still in the `Created` state. Public TAP methods should return active tasks, so callers shouldn't need to call `Start`. + +In most TAP code, don't dispose tasks. A doesn't hold unmanaged resources in the typical case, and disposing every task adds overhead without practical benefit. Dispose only when specific APIs or measurements show a need. + +If you start background work that outlives the immediate call path, keep ownership explicit and track completion. For more guidance, see [Keeping async methods alive](keeping-async-methods-alive.md). ### Generating TAP methods manually -You may implement the TAP pattern manually for better control over implementation. The compiler relies on the public surface area exposed from the namespace and supporting types in the namespace. To implement the TAP yourself, you create a object, perform the asynchronous operation, and when it completes, call the , , or method, or the `Try` version of one of these methods. When you implement a TAP method manually, you must complete the resulting task when the represented asynchronous operation completes. For example: +You might implement the TAP pattern manually for better control over implementation. The compiler relies on the public surface area exposed from the namespace and supporting types in the namespace. To implement the TAP yourself, you create a object, perform the asynchronous operation, and when it completes, call the , , or method, or the `Try` version of one of these methods. When you implement a TAP method manually, you must complete the resulting task when the represented asynchronous operation completes. For example: -[!code-csharp[Conceptual.TAP_Patterns#1](../../../samples/snippets/csharp/VS_Snippets_CLR/conceptual.tap_patterns/cs/patterns1.cs#1)] -[!code-vb[Conceptual.TAP_Patterns#1](../../../samples/snippets/visualbasic/VS_Snippets_CLR/conceptual.tap_patterns/vb/patterns1.vb#1)] +:::code language="csharp" source="./snippets/implementing-the-task-based-asynchronous-pattern/csharp/Program.cs" id="ManualTapImplementation"::: +:::code language="vb" source="./snippets/implementing-the-task-based-asynchronous-pattern/vb/Program.vb" id="ManualTapImplementation"::: ### Hybrid approach - You may find it useful to implement the TAP pattern manually but to delegate the core logic for the implementation to the compiler. For example, you may want to use the hybrid approach when you want to verify arguments outside a compiler-generated asynchronous method so that exceptions can escape to the method's direct caller rather than being exposed through the object: + You might find it useful to implement the TAP pattern manually but delegate the core logic for the implementation to the compiler. For example, you might want to use the hybrid approach when you want to verify arguments outside a compiler-generated asynchronous method so that exceptions can escape to the method's direct caller rather than being exposed through the object: - [!code-csharp[Conceptual.TAP_Patterns#2](../../../samples/snippets/csharp/VS_Snippets_CLR/conceptual.tap_patterns/cs/patterns1.cs#2)] - [!code-vb[Conceptual.TAP_Patterns#2](../../../samples/snippets/visualbasic/VS_Snippets_CLR/conceptual.tap_patterns/vb/patterns1.vb#2)] +:::code language="csharp" source="./snippets/implementing-the-task-based-asynchronous-pattern/csharp/Program.cs" id="HybridApproach"::: +:::code language="vb" source="./snippets/implementing-the-task-based-asynchronous-pattern/vb/Program.vb" id="HybridApproach"::: Another case where such delegation is useful is when you're implementing fast-path optimization and want to return a cached task. ## Workloads -You may implement both compute-bound and I/O-bound asynchronous operations as TAP methods. However, when TAP methods are exposed publicly from a library, they should be provided only for workloads that involve I/O-bound operations (they may also involve computation, but should not be purely computational). If a method is purely compute-bound, it should be exposed only as a synchronous implementation. The code that consumes it may then choose whether to wrap an invocation of that synchronous method into a task to offload the work to another thread or to achieve parallelism. And if a method is I/O-bound, it should be exposed only as an asynchronous implementation. +You can implement both compute-bound and I/O-bound asynchronous operations as TAP methods. However, when you expose TAP methods publicly from a library, provide them only for workloads that involve I/O-bound operations. These operations might also involve computation, but they shouldn't be purely computational. If a method is purely compute-bound, expose it only as a synchronous implementation. The code that consumes it can then choose whether to wrap an invocation of that synchronous method into a task to offload the work to another thread or to achieve parallelism. If a method is I/O-bound, expose it only as an asynchronous implementation. ### Compute-bound tasks -The class is ideally suited for representing computationally intensive operations. By default, it takes advantage of special support within the class to provide efficient execution, and it also provides significant control over when, where, and how asynchronous computations execute. +The class works well for representing computationally intensive operations. By default, it takes advantage of special support within the class to provide efficient execution. It also provides significant control over when, where, and how asynchronous computations execute. -You can generate compute-bound tasks in the following ways: +Generate compute-bound tasks in the following ways: -- In .NET Framework 4.5 and later versions (including .NET Core and .NET 5+), use the static method as a shortcut to . You may use to easily launch a compute-bound task that targets the thread pool. This is the preferred mechanism for launching a compute-bound task. Use `StartNew` directly only when you want more fine-grained control over the task. +- In .NET Framework 4.5 and later versions (including .NET Core and .NET 5+), use the static method as a shortcut to . Use to easily launch a compute-bound task that targets the thread pool. This method is the preferred mechanism for launching a compute-bound task. Use `StartNew` directly only when you want more fine-grained control over the task. -- In .NET Framework 4, use the method, which accepts a delegate (typically an or a ) to be executed asynchronously. If you provide an delegate, the method returns a object that represents the asynchronous execution of that delegate. If you provide a delegate, the method returns a object. Overloads of the method accept a cancellation token (), task creation options (), and a task scheduler (), all of which provide fine-grained control over the scheduling and execution of the task. A factory instance that targets the current task scheduler is available as a static property () of the class; for example: `Task.Factory.StartNew(…)`. +- In .NET Framework 4, use the method. It accepts a delegate (typically an or a ) to execute asynchronously. If you provide an delegate, the method returns a object that represents the asynchronous execution of that delegate. If you provide a delegate, the method returns a object. Overloads of the method accept a cancellation token (), task creation options (), and a task scheduler (). These parameters provide fine-grained control over the scheduling and execution of the task. A factory instance that targets the current task scheduler is available as a static property () of the class. For example: `Task.Factory.StartNew(…)`. -- Use the constructors of the `Task` type and the `Start` method if you want to generate and schedule the task separately. Public methods must only return tasks that have already been started. +- Use the constructors of the `Task` type and the `Start` method if you want to generate and schedule the task separately. Public methods must only return tasks that are already started. - Use the overloads of the method. This method creates a new task that is scheduled when another task completes. Some of the overloads accept a cancellation token, continuation options, and a task scheduler for better control over the scheduling and execution of the continuation task. - Use the and methods. These methods create a new task that is scheduled when all or any of a supplied set of tasks completes. These methods also provide overloads to control the scheduling and execution of these tasks. -In compute-bound tasks, the system can prevent the execution of a scheduled task if it receives a cancellation request before it starts running the task. As such, if you provide a cancellation token ( object), you can pass that token to the asynchronous code that monitors the token. You can also provide the token to one of the previously mentioned methods such as `StartNew` or `Run` so that the `Task` runtime may also monitor the token. +In compute-bound tasks, the system can prevent the execution of a scheduled task if it receives a cancellation request before it starts running the task. As such, if you provide a cancellation token ( object), you can pass that token to the asynchronous code that monitors the token. You can also provide the token to one of the previously mentioned methods such as `StartNew` or `Run` so that the `Task` runtime can also monitor the token. -For example, consider an asynchronous method that renders an image. The body of the task can poll the cancellation token so that the code may exit early if a cancellation request arrives during rendering. In addition, if the cancellation request arrives before rendering starts, you'll want to prevent the rendering operation: +For example, consider an asynchronous method that renders an image. The body of the task can poll the cancellation token so that the code exits early if a cancellation request arrives during rendering. In addition, if the cancellation request arrives before rendering starts, you want to prevent the rendering operation: -[!code-csharp[Conceptual.TAP_Patterns#3](../../../samples/snippets/csharp/VS_Snippets_CLR/conceptual.tap_patterns/cs/patterns1.cs#3)] -[!code-vb[Conceptual.TAP_Patterns#3](../../../samples/snippets/visualbasic/VS_Snippets_CLR/conceptual.tap_patterns/vb/patterns1.vb#3)] +:::code language="csharp" source="./snippets/implementing-the-task-based-asynchronous-pattern/csharp/Program.cs" id="ComputeBoundTask"::: +:::code language="vb" source="./snippets/implementing-the-task-based-asynchronous-pattern/vb/Program.vb" id="ComputeBoundTask"::: Compute-bound tasks end in a state if at least one of the following conditions is true: - A cancellation request arrives through the object, which is provided as an argument to the creation method (for example, `StartNew` or `Run`) before the task transitions to the state. -- An exception goes unhandled within the body of such a task, that exception contains the same that is passed to the task, and that token shows that cancellation is requested. +- An exception goes unhandled within the body of such a task. That exception contains the same that is passed to the task, and that token shows that cancellation is requested. -If another exception goes unhandled within the body of the task, the task ends in the state, and any attempts to wait on the task or access its result causes an exception to be thrown. +If another exception goes unhandled within the body of the task, the task ends in the state. Any attempts to wait on the task or access its result causes an exception to be thrown. ### I/O-bound tasks -To create a task that should not be directly backed by a thread for the entirety of its execution, use the type. This type exposes a property that returns an associated instance. The life cycle of this task is controlled by methods such as , , , and their `TrySet` variants. +To create a task that shouldn't directly use a thread for the entire execution, use the type. This type exposes a property that returns an associated instance. You control the life cycle of this task by using methods such as , , , and their `TrySet` variants. -Let's say that you want to create a task that will complete after a specified period of time. For example, you may want to delay an activity in the user interface. The class already provides the ability to asynchronously invoke a delegate after a specified period of time, and by using you can put a front on the timer, for example: +Suppose you want to create a task that completes after a specified period of time. For example, you might want to delay an activity in the user interface. The class already provides the ability to asynchronously invoke a delegate after a specified period of time. By using , you can put a front on the timer. For example: -[!code-csharp[Conceptual.TAP_Patterns#4](../../../samples/snippets/csharp/VS_Snippets_CLR/conceptual.tap_patterns/cs/patterns1.cs#4)] -[!code-vb[Conceptual.TAP_Patterns#4](../../../samples/snippets/visualbasic/VS_Snippets_CLR/conceptual.tap_patterns/vb/patterns1.vb#4)] +:::code language="csharp" source="./snippets/implementing-the-task-based-asynchronous-pattern/csharp/Program.cs" id="DelayWithTimer"::: +:::code language="vb" source="./snippets/implementing-the-task-based-asynchronous-pattern/vb/Program.vb" id="DelayWithTimer"::: -The method is provided for this purpose, and you can use it inside another asynchronous method, for example, to implement an asynchronous polling loop: +The method is provided for this purpose. You can use it inside another asynchronous method, for example, to implement an asynchronous polling loop: -[!code-csharp[Conceptual.TAP_Patterns#5](../../../samples/snippets/csharp/VS_Snippets_CLR/conceptual.tap_patterns/cs/patterns1.cs#5)] -[!code-vb[Conceptual.TAP_Patterns#5](../../../samples/snippets/visualbasic/VS_Snippets_CLR/conceptual.tap_patterns/vb/patterns1.vb#5)] +:::code language="csharp" source="./snippets/implementing-the-task-based-asynchronous-pattern/csharp/Program.cs" id="PollingLoop"::: +:::code language="vb" source="./snippets/implementing-the-task-based-asynchronous-pattern/vb/Program.vb" id="PollingLoop"::: -The class doesn't have a non-generic counterpart. However, derives from , so you can use the generic object for I/O-bound methods that simply return a task. To do this, you can use a source with a dummy `TResult` ( is a good default choice, but if you're concerned about the user of the downcasting it to a , you can use a private `TResult` type instead). For example, the `Delay` method in the previous example returns the current time along with the resulting offset (`Task`). If such a result value is unnecessary, the method could instead be coded as follows (note the change of return type and the change of argument to ): +The class doesn't have a non-generic counterpart. However, derives from , so you can use the generic object for I/O-bound methods that simply return a task. To do this, use a source with a dummy `TResult` ( is a good default choice, but if you're concerned about the user of the downcasting it to a , you can use a private `TResult` type instead). For example, the `Delay` method in the previous example returns the current time along with the resulting offset (`Task`). If such a result value is unnecessary, the method could instead be coded as follows (note the change of return type and the change of argument to ): -[!code-csharp[Conceptual.TAP_Patterns#6](../../../samples/snippets/csharp/VS_Snippets_CLR/conceptual.tap_patterns/cs/patterns1.cs#6)] -[!code-vb[Conceptual.TAP_Patterns#6](../../../samples/snippets/visualbasic/VS_Snippets_CLR/conceptual.tap_patterns/vb/patterns1.vb#6)] +:::code language="csharp" source="./snippets/implementing-the-task-based-asynchronous-pattern/csharp/Program.cs" id="DelayBoolResult"::: +:::code language="vb" source="./snippets/implementing-the-task-based-asynchronous-pattern/vb/Program.vb" id="DelayBoolResult"::: ### Mixed compute-bound and I/O-bound tasks -Asynchronous methods are not limited to just compute-bound or I/O-bound operations but may represent a mixture of the two. In fact, multiple asynchronous operations are often combined into larger mixed operations. For example, the `RenderAsync` method in a previous example performed a computationally intensive operation to render an image based on some input `imageData`. This `imageData` could come from a web service that you asynchronously access: +Asynchronous methods aren't limited to just compute-bound or I/O-bound operations. They can represent a mixture of the two. In fact, you often combine multiple asynchronous operations into larger mixed operations. For example, the `RenderAsync` method in a previous example performs a computationally intensive operation to render an image based on some input `imageData`. This `imageData` could come from a web service that you asynchronously access: -[!code-csharp[Conceptual.TAP_Patterns#7](../../../samples/snippets/csharp/VS_Snippets_CLR/conceptual.tap_patterns/cs/patterns1.cs#7)] -[!code-vb[Conceptual.TAP_Patterns#7](../../../samples/snippets/visualbasic/VS_Snippets_CLR/conceptual.tap_patterns/vb/patterns1.vb#7)] +:::code language="csharp" source="./snippets/implementing-the-task-based-asynchronous-pattern/csharp/Program.cs" id="MixedBoundTask"::: +:::code language="vb" source="./snippets/implementing-the-task-based-asynchronous-pattern/vb/Program.vb" id="MixedBoundTask"::: -This example also demonstrates how a single cancellation token may be threaded through multiple asynchronous operations. For more information, see the cancellation usage section in [Consuming the Task-based Asynchronous Pattern](consuming-the-task-based-asynchronous-pattern.md). +This example also demonstrates how a single cancellation token can be threaded through multiple asynchronous operations. For more information, see the cancellation usage section in [Consuming the Task-based Asynchronous Pattern](consuming-the-task-based-asynchronous-pattern.md). ## See also diff --git a/docs/standard/asynchronous-programming-patterns/keeping-async-methods-alive.md b/docs/standard/asynchronous-programming-patterns/keeping-async-methods-alive.md new file mode 100644 index 0000000000000..472d09d43c6ad --- /dev/null +++ b/docs/standard/asynchronous-programming-patterns/keeping-async-methods-alive.md @@ -0,0 +1,85 @@ +--- +title: "Keeping async methods alive" +description: Learn how to avoid lifetime bugs in fire-and-forget async code by keeping task ownership explicit, observing failures, and coordinating shutdown. +ms.date: 04/15/2026 +ai-usage: ai-assisted +dev_langs: + - "csharp" + - "vb" +helpviewer_keywords: + - "fire-and-forget" + - "async lifetime" + - "unobserved task exception" + - "background task tracking" + - "TAP best practices" +--- +# Keeping async methods alive + +Fire-and-forget work is easy to start and easy to lose. If you start an asynchronous operation and drop the returned , you lose visibility into completion, cancellation, and failures. + +Most lifetime bugs in async code are ownership bugs, not compiler bugs. The `async` state machine and its `Task` stay alive while work is still reachable through continuations. Problems happen when your app no longer tracks that work. + +## Why fire-and-forget causes lifetime bugs + +When you start background work without tracking it, you create three risks: + +- The operation can fail, and nobody observes the exception. +- The process or host can shut down before the operation finishes. +- The operation can outlive the object or scope that was meant to control it. + +Use fire-and-forget only when the work is truly optional and failure is acceptable. + +## Bad and good fire-and-forget patterns + +The following example starts untracked work and then shows a tracked alternative: + +:::code language="csharp" source="./snippets/keeping-async-methods-alive/csharp/Program.cs" id="FireAndForgetPitfall"::: +:::code language="vb" source="./snippets/keeping-async-methods-alive/vb/Program.vb" id="FireAndForgetPitfall"::: + +:::code language="csharp" source="./snippets/keeping-async-methods-alive/csharp/Program.cs" id="FireAndForgetFix"::: +:::code language="vb" source="./snippets/keeping-async-methods-alive/vb/Program.vb" id="FireAndForgetFix"::: + +Track long-running work in an owner component and await tracked tasks during shutdown. + +## Keep ownership explicit + +Use one of these ownership models: + +- Return the `Task` and require callers to await it. +- Track background tasks in a dedicated owner service. +- Use a host-managed background abstraction so the host owns lifetime. + +If work must continue after the caller returns, transfer ownership explicitly. For example, hand the task to a tracker that logs errors and participates in shutdown. + +## Surface exceptions from background tasks + +Dropped tasks can fail silently until finalization and unobserved-exception handling occurs. That timing is non-deterministic and too late for normal request or workflow handling. + +Attach observation logic when you queue background work. At minimum, log failures in a continuation. Prefer a centralized tracker so every queued operation gets the same policy. + +For exception propagation details, see [Task exception handling](task-exception-handling.md). + +## Coordinate cancellation and shutdown + +Tie background work to a cancellation token that represents app or operation lifetime. During shutdown: + +1. Stop accepting new work. +1. Signal cancellation. +1. Await tracked tasks with a bounded timeout. +1. Log incomplete operations. + +This flow keeps shutdown predictable and prevents partial writes or orphaned operations. + +## Can the GC collect an async method before it finishes? + +The runtime keeps the async state machine alive while continuations still reference it. You usually don't lose an in-flight async operation to garbage collection of the state machine itself. + +You can still lose correctness if you lose ownership of the returned task, dispose required resources early, or let the process end before completion. Focus on task ownership and coordinated shutdown. + +## See also + +- [Task-based asynchronous pattern (TAP)](task-based-asynchronous-pattern-tap.md) +- [Consuming the Task-based Asynchronous Pattern](consuming-the-task-based-asynchronous-pattern.md) +- [Common async/await bugs](common-async-bugs.md) +- [ExecutionContext and SynchronizationContext](executioncontext-synchronizationcontext.md) +- [Task exception handling](task-exception-handling.md) diff --git a/docs/standard/asynchronous-programming-patterns/snippets/consuming-the-task-based-asynchronous-pattern/csharp/Program.cs b/docs/standard/asynchronous-programming-patterns/snippets/consuming-the-task-based-asynchronous-pattern/csharp/Program.cs new file mode 100644 index 0000000000000..9f2045b0a5e9b --- /dev/null +++ b/docs/standard/asynchronous-programming-patterns/snippets/consuming-the-task-based-asynchronous-pattern/csharp/Program.cs @@ -0,0 +1,666 @@ +using System.Collections.Concurrent; +using System.Drawing; +using System.Net; +using System.Threading.Tasks.Dataflow; + +// Stub types/methods used throughout examples +static class Stubs +{ + public static Task DownloadStringTaskAsync(string url, CancellationToken ct = default) => Task.FromResult(""); + public static Task DownloadDataAsync(string url, CancellationToken ct = default) => Task.FromResult(Array.Empty()); + public static Task SaveToDiskAsync(string path, byte[] data, CancellationToken ct) => Task.CompletedTask; + public static Task SendMailAsync(string addr, CancellationToken ct = default) => Task.CompletedTask; + public static Task GetBuyRecommendation1Async(string symbol, CancellationToken ct = default) => Task.FromResult(true); + public static Task GetBuyRecommendation2Async(string symbol, CancellationToken ct = default) => Task.FromResult(true); + public static Task GetBuyRecommendation3Async(string symbol, CancellationToken ct = default) => Task.FromResult(true); + public static Task GetCurrentPriceFromServer1Async(string symbol, CancellationToken ct) => Task.FromResult(100.0); + public static Task GetCurrentPriceFromServer2Async(string symbol, CancellationToken ct) => Task.FromResult(100.0); + public static Task GetCurrentPriceFromServer3Async(string symbol, CancellationToken ct) => Task.FromResult(100.0); + public static Task GetBitmapAsync(string url, CancellationToken ct = default) => Task.FromResult(new Bitmap(1, 1)); + public static Bitmap ConvertImage(Bitmap bmp) => bmp; + public static Bitmap Mashup(Bitmap b1, Bitmap b2) => b1; + public static Task DownloadFirstImageAsync() => Task.FromResult(new Bitmap(1, 1)); + public static Task DownloadSecondImageAsync() => Task.FromResult(new Bitmap(1, 1)); + public static void BuyStock(string symbol) { } + public static void Log(object? o) { } + public static void ProcessNextItem(int item) { } + public static bool TryGetCachedValue(out int value) { value = 0; return false; } + public static string[] addrs = Array.Empty(); + public static string[] urls = Array.Empty(); +} + +static class Examples +{ + // ---- Yield example ---- + // + public static async Task YieldLoopExample() + { + await Task.Run(async delegate + { + for (int i = 0; i < 1000000; i++) + { + await Task.Yield(); // fork the continuation into a separate work item + } + }); + } + // + + // ---- Task.Run examples ---- + // + public static async Task TaskRunBasicExample() + { + int answer = 42; + string result = await Task.Run(() => + { + // … do compute-bound work here + return answer.ToString(); + }); + Console.WriteLine(result); + } + // + + // + public static async Task TaskRunAsyncExample() + { + Bitmap image = await Task.Run(async () => + { + using Bitmap bmp1 = await Stubs.DownloadFirstImageAsync(); + using Bitmap bmp2 = await Stubs.DownloadSecondImageAsync(); + return Stubs.Mashup(bmp1, bmp2); + }); + } + // + + // ---- Task.FromResult ---- + // + public static Task GetValueAsync(string key) + { + int cachedValue; + return Stubs.TryGetCachedValue(out cachedValue) ? + Task.FromResult(cachedValue) : + GetValueAsyncInternal(key); + } + + static async Task GetValueAsyncInternal(string key) + { + await Task.Delay(1); + return 0; + } + // + + // ---- Task.WhenAll ---- + // + public static async Task WhenAllBasic() + { + IEnumerable asyncOps = from addr in Stubs.addrs select Stubs.SendMailAsync(addr); + await Task.WhenAll(asyncOps); + } + // + + // + public static async Task WhenAllWithCatch() + { + IEnumerable asyncOps = from addr in Stubs.addrs select Stubs.SendMailAsync(addr); + try + { + await Task.WhenAll(asyncOps); + } + catch (Exception exc) + { + Console.WriteLine(exc); + } + } + // + + // + public static async Task WhenAllExamineExceptions() + { + Task[] asyncOps = (from addr in Stubs.addrs select Stubs.SendMailAsync(addr)).ToArray(); + try + { + await Task.WhenAll(asyncOps); + } + catch (Exception exc) + { + foreach (Task faulted in asyncOps.Where(t => t.IsFaulted)) + { + Console.WriteLine($"Faulted: {faulted.Exception}"); + } + } + } + // + + // + public static async Task WhenAllDownloadPages() + { + string[] pages = await Task.WhenAll( + from url in Stubs.urls select Stubs.DownloadStringTaskAsync(url)); + Console.WriteLine(pages.Length); + } + // + + // + public static async Task WhenAllDownloadPagesExceptions() + { + Task[] asyncOps = + (from url in Stubs.urls select Stubs.DownloadStringTaskAsync(url)).ToArray(); + try + { + string[] pages = await Task.WhenAll(asyncOps); + Console.WriteLine(pages.Length); + } + catch (Exception exc) + { + foreach (Task faulted in asyncOps.Where(t => t.IsFaulted)) + { + Console.WriteLine($"Faulted: {faulted.Exception}"); + } + } + } + // + + // ---- Task.WhenAny ---- + // + public static async Task WhenAnyRedundancy(string symbol) + { + var recommendations = new List>() + { + Stubs.GetBuyRecommendation1Async(symbol), + Stubs.GetBuyRecommendation2Async(symbol), + Stubs.GetBuyRecommendation3Async(symbol) + }; + Task recommendation = await Task.WhenAny(recommendations); + if (await recommendation) Stubs.BuyStock(symbol); + } + // + + // + public static async Task WhenAnyRetryOnException(string symbol) + { + Task[] allRecommendations = new Task[] + { + Stubs.GetBuyRecommendation1Async(symbol), + Stubs.GetBuyRecommendation2Async(symbol), + Stubs.GetBuyRecommendation3Async(symbol) + }; + var remaining = allRecommendations.ToList(); + while (remaining.Count > 0) + { + Task recommendation = await Task.WhenAny(remaining); + try + { + if (await recommendation) Stubs.BuyStock(symbol); + break; + } + catch (WebException) + { + remaining.Remove(recommendation); + } + } + } + // + + // + public static async Task WhenAnyCancelRemainder(string symbol) + { + var cts = new CancellationTokenSource(); + var recommendations = new List>() + { + Stubs.GetBuyRecommendation1Async(symbol, cts.Token), + Stubs.GetBuyRecommendation2Async(symbol, cts.Token), + Stubs.GetBuyRecommendation3Async(symbol, cts.Token) + }; + + Task recommendation = await Task.WhenAny(recommendations); + cts.Cancel(); + if (await recommendation) Stubs.BuyStock(symbol); + } + // + + // + public static async Task WhenAnyInterleaving(string[] imageUrls) + { + List> imageTasks = + (from imageUrl in imageUrls select Stubs.GetBitmapAsync(imageUrl)).ToList(); + while (imageTasks.Count > 0) + { + try + { + Task imageTask = await Task.WhenAny(imageTasks); + imageTasks.Remove(imageTask); + + Bitmap image = await imageTask; + Console.WriteLine($"Got image: {image.Width}x{image.Height}"); + } + catch { } + } + } + // + + // + public static async Task WhenAnyInterleavingWithProcessing(string[] imageUrls) + { + List> imageTasks = + (from imageUrl in imageUrls + select Stubs.GetBitmapAsync(imageUrl) + .ContinueWith(t => Stubs.ConvertImage(t.Result))).ToList(); + while (imageTasks.Count > 0) + { + try + { + Task imageTask = await Task.WhenAny(imageTasks); + imageTasks.Remove(imageTask); + + Bitmap image = await imageTask; + Console.WriteLine($"Got image: {image.Width}x{image.Height}"); + } + catch { } + } + } + // + + // + public static async Task WhenAnyThrottling(Uri[] uriList) + { + const int CONCURRENCY_LEVEL = 15; + int nextIndex = 0; + var imageTasks = new List>(); + while (nextIndex < CONCURRENCY_LEVEL && nextIndex < uriList.Length) + { + imageTasks.Add(Stubs.GetBitmapAsync(uriList[nextIndex].ToString())); + nextIndex++; + } + + while (imageTasks.Count > 0) + { + try + { + Task imageTask = await Task.WhenAny(imageTasks); + imageTasks.Remove(imageTask); + + Bitmap image = await imageTask; + Console.WriteLine($"Got image: {image.Width}x{image.Height}"); + } + catch (Exception exc) { Stubs.Log(exc); } + + if (nextIndex < uriList.Length) + { + imageTasks.Add(Stubs.GetBitmapAsync(uriList[nextIndex].ToString())); + nextIndex++; + } + } + } + // + + // + public static async Task UntilCompletionOrCancellation( + Task asyncOp, CancellationToken ct) + { + var tcs = new TaskCompletionSource(); + using (ct.Register(() => tcs.TrySetResult(true))) + await Task.WhenAny(asyncOp, tcs.Task); + return asyncOp; + } + // + + // ---- LogCompletionIfFailed ---- + // + private static async void LogCompletionIfFailed(IEnumerable tasks) + { + foreach (var task in tasks) + { + try { await task; } + catch (Exception exc) { Stubs.Log(exc); } + } + } + // + + // + public static async Task DownloadWithTimeout(string url) + { + Task download = Stubs.GetBitmapAsync(url); + if (download == await Task.WhenAny(download, Task.Delay(3000))) + { + return await download; + } + else + { + var ignored = download.ContinueWith( + t => Trace($"Task finally completed: {t.Status}")); + return null; + } + } + + static void Trace(string message) => Console.WriteLine(message); + // + + // + public static async Task DownloadMultipleWithTimeout(string[] imageUrls) + { + Task downloads = + Task.WhenAll(from url in imageUrls select Stubs.GetBitmapAsync(url)); + if (downloads == await Task.WhenAny(downloads, Task.Delay(3000))) + { + return downloads.Result; + } + else + { + downloads.ContinueWith(t => Stubs.Log(t)); + return null; + } + } + // + + // ---- Combinators ---- + // + public static T RetryOnFault(Func function, int maxTries) + { + for (int i = 0; i < maxTries; i++) + { + try { return function(); } + catch { if (i == maxTries - 1) throw; } + } + return default(T)!; + } + // + + // + public static async Task RetryOnFault(Func> function, int maxTries) + { + for (int i = 0; i < maxTries; i++) + { + try { return await function().ConfigureAwait(false); } + catch { if (i == maxTries - 1) throw; } + } + return default(T)!; + } + // + + // + public static async Task RetryOnFaultWithDelay( + Func> function, int maxTries, Func retryWhen) + { + for (int i = 0; i < maxTries; i++) + { + try { return await function().ConfigureAwait(false); } + catch { if (i == maxTries - 1) throw; } + await retryWhen().ConfigureAwait(false); + } + return default(T)!; + } + // + + // + public static async Task NeedOnlyOne( + params Func>[] functions) + { + var cts = new CancellationTokenSource(); + var tasks = (from function in functions + select function(cts.Token)).ToArray(); + var completed = await Task.WhenAny(tasks).ConfigureAwait(false); + cts.Cancel(); + foreach (var task in tasks) + { + var ignored = task.ContinueWith( + t => Stubs.Log(t), TaskContinuationOptions.OnlyOnFaulted); + } + return await completed; + } + // + + // + public static IEnumerable> Interleaved(IEnumerable> tasks) + { + var inputTasks = tasks.ToList(); + var sources = (from _ in Enumerable.Range(0, inputTasks.Count) + select new TaskCompletionSource()).ToList(); + int nextTaskIndex = -1; + foreach (var inputTask in inputTasks) + { + inputTask.ContinueWith(completed => + { + var source = sources[Interlocked.Increment(ref nextTaskIndex)]; + if (completed.IsFaulted) + source.TrySetException(completed.Exception!.InnerExceptions); + else if (completed.IsCanceled) + source.TrySetCanceled(); + else + source.TrySetResult(completed.Result); + }, CancellationToken.None, + TaskContinuationOptions.ExecuteSynchronously, + TaskScheduler.Default); + } + return from source in sources + select source.Task; + } + // + + // + public static Task WhenAllOrFirstException(IEnumerable> tasks) + { + var inputs = tasks.ToList(); + var ce = new CountdownEvent(inputs.Count); + var tcs = new TaskCompletionSource(); + + Action onCompleted = (Task completed) => + { + if (completed.IsFaulted) + tcs.TrySetException(completed.Exception!.InnerExceptions); + if (ce.Signal() && !tcs.Task.IsCompleted) + tcs.TrySetResult(inputs.Select(t => ((Task)t).Result).ToArray()); + }; + + foreach (var t in inputs) t.ContinueWith(onCompleted); + return tcs.Task; + } + // + + // ---- AsyncCache usage ---- + // + static AsyncCache m_webPages = + new AsyncCache(url => Stubs.DownloadStringTaskAsync(url)); + + public static async Task UseWebPageCache(string url) + { + string contents = await m_webPages[url]; + Console.WriteLine(contents.Length); + } + // + + // ---- AsyncProducerConsumerCollection usage ---- + // + static AsyncProducerConsumerCollection m_data = new(); + + public static async Task ConsumerAsync() + { + while (true) + { + int nextItem = await m_data.Take(); + Stubs.ProcessNextItem(nextItem); + } + } + + public static void Produce(int data) + { + m_data.Add(data); + } + // + + // + static BufferBlock m_dataBlock = new(); + + public static async Task ConsumerAsyncBlock() + { + while (true) + { + int nextItem = await m_dataBlock.ReceiveAsync(); + Stubs.ProcessNextItem(nextItem); + } + } + + public static void ProduceBlock(int data) + { + m_dataBlock.Post(data); + } + // + + // ---- Cancellation examples ---- + // + public static async Task CancelSingleDownload(string url) + { + var cts = new CancellationTokenSource(); + string result = await Stubs.DownloadStringTaskAsync(url, cts.Token); + // at some point later, potentially on another thread + cts.Cancel(); + Console.WriteLine(result); + } + // + + // + public static async Task CancelMultipleDownloads(string[] urlList) + { + var cts = new CancellationTokenSource(); + IList results = await Task.WhenAll( + from url in urlList select Stubs.DownloadStringTaskAsync(url, cts.Token)); + // at some point later, potentially on another thread + cts.Cancel(); + Console.WriteLine(results.Count); + } + // + + // + public static async Task CancelSubsetDownloads(string url, string outputPath) + { + var cts = new CancellationTokenSource(); + byte[] data = await Stubs.DownloadDataAsync(url, cts.Token); + await Stubs.SaveToDiskAsync(outputPath, data, CancellationToken.None); + // at some point later, potentially on another thread + cts.Cancel(); + } + // +} + +// ---- AsyncCache ---- +// +public class AsyncCache where TKey : notnull +{ + private readonly Func> _valueFactory; + private readonly ConcurrentDictionary>> _map; + + public AsyncCache(Func> valueFactory) + { + if (valueFactory == null) throw new ArgumentNullException(nameof(valueFactory)); + _valueFactory = valueFactory; + _map = new ConcurrentDictionary>>(); + } + + public Task this[TKey key] + { + get + { + if (key == null) throw new ArgumentNullException(nameof(key)); + return _map.GetOrAdd(key, toAdd => + new Lazy>(() => _valueFactory(toAdd))).Value; + } + } +} +// + +// ---- AsyncProducerConsumerCollection ---- +// +public class AsyncProducerConsumerCollection +{ + private readonly Queue m_collection = new Queue(); + private readonly Queue> m_waiting = + new Queue>(); + + public void Add(T item) + { + TaskCompletionSource? tcs = null; + lock (m_collection) + { + if (m_waiting.Count > 0) tcs = m_waiting.Dequeue(); + else m_collection.Enqueue(item); + } + if (tcs != null) tcs.TrySetResult(item); + } + + public Task Take() + { + lock (m_collection) + { + if (m_collection.Count > 0) + { + return Task.FromResult(m_collection.Dequeue()); + } + else + { + var tcs = new TaskCompletionSource(); + m_waiting.Enqueue(tcs); + return tcs.Task; + } + } + } +} +// + +// +class EarlyBailoutUI +{ + private CancellationTokenSource? m_cts; + + public void btnCancel_Click(object sender, EventArgs e) + { + if (m_cts != null) m_cts.Cancel(); + } + + public async void btnRun_Click(object sender, EventArgs e) + { + m_cts = new CancellationTokenSource(); + try + { + Task imageDownload = Stubs.GetBitmapAsync("url"); + await Examples.UntilCompletionOrCancellation(imageDownload, m_cts.Token); + if (imageDownload.IsCompleted) + { + Bitmap image = await imageDownload; + Stubs.Log(image); + } + else imageDownload.ContinueWith(t => Stubs.Log(t)); + } + finally { } + } +} +// + +// +class EarlyBailoutWithTokenUI +{ + private CancellationTokenSource? m_cts; + + public async void btnRun_Click(object sender, EventArgs e) + { + m_cts = new CancellationTokenSource(); + try + { + Task imageDownload = Stubs.GetBitmapAsync("url", m_cts.Token); + await Examples.UntilCompletionOrCancellation(imageDownload, m_cts.Token); + Bitmap image = await imageDownload; + Stubs.Log(image); + } + catch (OperationCanceledException) { } + finally { } + } +} +// + +public static class Program +{ + public static async Task Main() + { + await Examples.WhenAllBasic(); + Console.WriteLine("Done."); + } +} + + diff --git a/docs/standard/asynchronous-programming-patterns/snippets/consuming-the-task-based-asynchronous-pattern/csharp/consuming-the-task-based-asynchronous-pattern.csproj b/docs/standard/asynchronous-programming-patterns/snippets/consuming-the-task-based-asynchronous-pattern/csharp/consuming-the-task-based-asynchronous-pattern.csproj new file mode 100644 index 0000000000000..565624abe6e75 --- /dev/null +++ b/docs/standard/asynchronous-programming-patterns/snippets/consuming-the-task-based-asynchronous-pattern/csharp/consuming-the-task-based-asynchronous-pattern.csproj @@ -0,0 +1,16 @@ + + + + Exe + net10.0 + enable + enable + CA1416 + + + + + + + + diff --git a/docs/standard/asynchronous-programming-patterns/snippets/consuming-the-task-based-asynchronous-pattern/vb/Program.vb b/docs/standard/asynchronous-programming-patterns/snippets/consuming-the-task-based-asynchronous-pattern/vb/Program.vb new file mode 100644 index 0000000000000..0f59502df1807 --- /dev/null +++ b/docs/standard/asynchronous-programming-patterns/snippets/consuming-the-task-based-asynchronous-pattern/vb/Program.vb @@ -0,0 +1,633 @@ +Imports System +Imports System.Collections.Concurrent +Imports System.Collections.Generic +Imports System.Drawing +Imports System.Net +Imports System.Threading +Imports System.Threading.Tasks +Imports System.Threading.Tasks.Dataflow + +' Stub types/methods used throughout examples +Module Stubs + Public Function DownloadStringTaskAsync(url As String, Optional ct As CancellationToken = Nothing) As Task(Of String) + Return Task.FromResult("") + End Function + + Public Function DownloadDataAsync(url As String, Optional ct As CancellationToken = Nothing) As Task(Of Byte()) + Return Task.FromResult(Array.Empty(Of Byte)()) + End Function + + Public Function SaveToDiskAsync(path As String, data As Byte(), ct As CancellationToken) As Task + Return Task.CompletedTask + End Function + + Public Function SendMailAsync(addr As String, Optional ct As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function + + Public Function GetBuyRecommendation1Async(symbol As String, Optional ct As CancellationToken = Nothing) As Task(Of Boolean) + Return Task.FromResult(True) + End Function + + Public Function GetBuyRecommendation2Async(symbol As String, Optional ct As CancellationToken = Nothing) As Task(Of Boolean) + Return Task.FromResult(True) + End Function + + Public Function GetBuyRecommendation3Async(symbol As String, Optional ct As CancellationToken = Nothing) As Task(Of Boolean) + Return Task.FromResult(True) + End Function + + Public Function GetBitmapAsync(url As String, Optional ct As CancellationToken = Nothing) As Task(Of Bitmap) + Return Task.FromResult(New Bitmap(1, 1)) + End Function + + Public Function ConvertImage(bmp As Bitmap) As Bitmap + Return bmp + End Function + + Public Function Mashup(b1 As Bitmap, b2 As Bitmap) As Bitmap + Return b1 + End Function + + Public Function DownloadFirstImageAsync() As Task(Of Bitmap) + Return Task.FromResult(New Bitmap(1, 1)) + End Function + + Public Function DownloadSecondImageAsync() As Task(Of Bitmap) + Return Task.FromResult(New Bitmap(1, 1)) + End Function + + Public Sub BuyStock(symbol As String) + End Sub + + Public Sub Log(o As Object) + End Sub + + Public Sub ProcessNextItem(item As Integer) + End Sub + + Public Function TryGetCachedValue(ByRef value As Integer) As Boolean + value = 0 + Return False + End Function + + Public addrs As String() = Array.Empty(Of String)() + Public urls As String() = Array.Empty(Of String)() +End Module + +Module Examples + + ' ---- Yield example ---- + ' + Public Async Function YieldLoopExample() As Task + Await Task.Run(Async Sub() + For i As Integer = 0 To 999999 + Await Task.Yield() ' fork the continuation into a separate work item + Next + End Sub) + End Function + ' + + ' ---- Task.Run examples ---- + ' + Public Async Function TaskRunBasicExample() As Task + Dim answer As Integer = 42 + Dim result As String = Await Task.Run(Function() + ' … do compute-bound work here + Return answer.ToString() + End Function) + Console.WriteLine(result) + End Function + ' + + ' + Public Async Function TaskRunAsyncExample() As Task + Dim image As Bitmap = Await Task.Run(Async Function() + Using bmp1 As Bitmap = Await Stubs.DownloadFirstImageAsync() + Using bmp2 As Bitmap = Await Stubs.DownloadSecondImageAsync() + Return Stubs.Mashup(bmp1, bmp2) + End Using + End Using + End Function) + End Function + ' + + ' ---- Task.FromResult ---- + ' + Public Function GetValueAsync(key As String) As Task(Of Integer) + Dim cachedValue As Integer + If Stubs.TryGetCachedValue(cachedValue) Then + Return Task.FromResult(cachedValue) + Else + Return GetValueAsyncInternal(key) + End If + End Function + + Private Async Function GetValueAsyncInternal(key As String) As Task(Of Integer) + Await Task.Delay(1) + Return 0 + End Function + ' + + ' ---- Task.WhenAll ---- + ' + Public Async Function WhenAllBasic() As Task + Dim asyncOps As IEnumerable(Of Task) = From addr In Stubs.addrs Select Stubs.SendMailAsync(addr) + Await Task.WhenAll(asyncOps) + End Function + ' + + ' + Public Async Function WhenAllWithCatch() As Task + Dim asyncOps As IEnumerable(Of Task) = From addr In Stubs.addrs Select Stubs.SendMailAsync(addr) + Try + Await Task.WhenAll(asyncOps) + Catch exc As Exception + Console.WriteLine(exc) + End Try + End Function + ' + + ' + Public Async Function WhenAllExamineExceptions() As Task + Dim asyncOps As Task() = (From addr In Stubs.addrs Select Stubs.SendMailAsync(addr)).ToArray() + Try + Await Task.WhenAll(asyncOps) + Catch exc As Exception + For Each faulted As Task In asyncOps.Where(Function(t) t.IsFaulted) + Console.WriteLine($"Faulted: {faulted.Exception}") + Next + End Try + End Function + ' + + ' + Public Async Function WhenAllDownloadPages() As Task + Dim pages As String() = Await Task.WhenAll( + From url In Stubs.urls Select Stubs.DownloadStringTaskAsync(url)) + Console.WriteLine(pages.Length) + End Function + ' + + ' + Public Async Function WhenAllDownloadPagesExceptions() As Task + Dim asyncOps As Task(Of String)() = + (From url In Stubs.urls Select Stubs.DownloadStringTaskAsync(url)).ToArray() + Try + Dim pages As String() = Await Task.WhenAll(asyncOps) + Console.WriteLine(pages.Length) + Catch exc As Exception + For Each faulted As Task(Of String) In asyncOps.Where(Function(t) t.IsFaulted) + Console.WriteLine($"Faulted: {faulted.Exception}") + Next + End Try + End Function + ' + + ' ---- Task.WhenAny ---- + ' + Public Async Function WhenAnyRedundancy(symbol As String) As Task + Dim recommendations As New List(Of Task(Of Boolean)) From { + Stubs.GetBuyRecommendation1Async(symbol), + Stubs.GetBuyRecommendation2Async(symbol), + Stubs.GetBuyRecommendation3Async(symbol) + } + Dim recommendation As Task(Of Boolean) = Await Task.WhenAny(recommendations) + If Await recommendation Then Stubs.BuyStock(symbol) + End Function + ' + + ' + Public Async Function WhenAnyRetryOnException(symbol As String) As Task + Dim allRecommendations As Task(Of Boolean)() = { + Stubs.GetBuyRecommendation1Async(symbol), + Stubs.GetBuyRecommendation2Async(symbol), + Stubs.GetBuyRecommendation3Async(symbol) + } + Dim remaining As List(Of Task(Of Boolean)) = allRecommendations.ToList() + While remaining.Count > 0 + Dim recommendation As Task(Of Boolean) = Await Task.WhenAny(remaining) + Try + If Await recommendation Then Stubs.BuyStock(symbol) + Exit While + Catch ex As WebException + remaining.Remove(recommendation) + End Try + End While + End Function + ' + + ' + Public Async Function WhenAnyCancelRemainder(symbol As String) As Task + Dim cts As New CancellationTokenSource() + Dim recommendations As New List(Of Task(Of Boolean)) From { + Stubs.GetBuyRecommendation1Async(symbol, cts.Token), + Stubs.GetBuyRecommendation2Async(symbol, cts.Token), + Stubs.GetBuyRecommendation3Async(symbol, cts.Token) + } + + Dim recommendation As Task(Of Boolean) = Await Task.WhenAny(recommendations) + cts.Cancel() + If Await recommendation Then Stubs.BuyStock(symbol) + End Function + ' + + ' + Public Async Function WhenAnyInterleaving(imageUrls As String()) As Task + Dim imageTasks As List(Of Task(Of Bitmap)) = + (From imageUrl In imageUrls Select Stubs.GetBitmapAsync(imageUrl)).ToList() + While imageTasks.Count > 0 + Try + Dim imageTask As Task(Of Bitmap) = Await Task.WhenAny(imageTasks) + imageTasks.Remove(imageTask) + + Dim image As Bitmap = Await imageTask + Console.WriteLine($"Got image: {image.Width}x{image.Height}") + Catch + End Try + End While + End Function + ' + + ' + Public Async Function WhenAnyInterleavingWithProcessing(imageUrls As String()) As Task + Dim imageTasks As List(Of Task(Of Bitmap)) = + (From imageUrl In imageUrls + Select Stubs.GetBitmapAsync(imageUrl).ContinueWith(Function(t) Stubs.ConvertImage(t.Result))).ToList() + While imageTasks.Count > 0 + Try + Dim imageTask As Task(Of Bitmap) = Await Task.WhenAny(imageTasks) + imageTasks.Remove(imageTask) + + Dim image As Bitmap = Await imageTask + Console.WriteLine($"Got image: {image.Width}x{image.Height}") + Catch + End Try + End While + End Function + ' + + ' + Public Async Function WhenAnyThrottling(uriList As Uri()) As Task + Const CONCURRENCY_LEVEL As Integer = 15 + Dim nextIndex As Integer = 0 + Dim imageTasks As New List(Of Task(Of Bitmap)) + While nextIndex < CONCURRENCY_LEVEL AndAlso nextIndex < uriList.Length + imageTasks.Add(Stubs.GetBitmapAsync(uriList(nextIndex).ToString())) + nextIndex += 1 + End While + + While imageTasks.Count > 0 + Try + Dim imageTask As Task(Of Bitmap) = Await Task.WhenAny(imageTasks) + imageTasks.Remove(imageTask) + + Dim image As Bitmap = Await imageTask + Console.WriteLine($"Got image: {image.Width}x{image.Height}") + Catch exc As Exception + Stubs.Log(exc) + End Try + + If nextIndex < uriList.Length Then + imageTasks.Add(Stubs.GetBitmapAsync(uriList(nextIndex).ToString())) + nextIndex += 1 + End If + End While + End Function + ' + + ' + Public Async Function UntilCompletionOrCancellation( + asyncOp As Task, ct As CancellationToken) As Task(Of Task) + Dim tcs As New TaskCompletionSource(Of Boolean)() + Using ct.Register(Sub() tcs.TrySetResult(True)) + Await Task.WhenAny(asyncOp, tcs.Task) + End Using + Return asyncOp + End Function + ' + + ' ---- LogCompletionIfFailed ---- + ' + Private Async Sub LogCompletionIfFailed(tasks As IEnumerable(Of Task)) + For Each task In tasks + Try + Await task + Catch exc As Exception + Stubs.Log(exc) + End Try + Next + End Sub + ' + + ' ---- Task.Delay timeout ---- + Private Sub TraceMsg(message As String) + Console.WriteLine(message) + End Sub + + ' + Public Async Function DownloadWithTimeout(url As String) As Task(Of Bitmap) + Dim download As Task(Of Bitmap) = Stubs.GetBitmapAsync(url) + If download Is Await Task.WhenAny(download, Task.Delay(3000)) Then + Return Await download + Else + Dim ignored = download.ContinueWith(Sub(t) TraceMsg($"Task finally completed: {t.Status}")) + Return Nothing + End If + End Function + ' + + ' + Public Async Function DownloadMultipleWithTimeout(imageUrls As String()) As Task(Of Bitmap()) + Dim downloads As Task(Of Bitmap()) = + Task.WhenAll(From url In imageUrls Select Stubs.GetBitmapAsync(url)) + If downloads Is Await Task.WhenAny(downloads, Task.Delay(3000)) Then + Return downloads.Result + Else + downloads.ContinueWith(Sub(t) Stubs.Log(t)) + Return Nothing + End If + End Function + ' + + ' ---- Combinators ---- + ' + Public Function RetryOnFaultSync(Of T)(func As Func(Of T), maxTries As Integer) As T + For i As Integer = 0 To maxTries - 1 + Try + Return func() + Catch + If i = maxTries - 1 Then Throw + End Try + Next + Return Nothing + End Function + ' + + ' + Public Async Function RetryOnFault(Of T)(func As Func(Of Task(Of T)), maxTries As Integer) As Task(Of T) + For i As Integer = 0 To maxTries - 1 + Try + Return Await func().ConfigureAwait(False) + Catch + If i = maxTries - 1 Then Throw + End Try + Next + Return Nothing + End Function + ' + + ' + Public Async Function RetryOnFaultWithDelay(Of T)( + func As Func(Of Task(Of T)), maxTries As Integer, retryWhen As Func(Of Task)) As Task(Of T) + For i As Integer = 0 To maxTries - 1 + Try + Return Await func().ConfigureAwait(False) + Catch + If i = maxTries - 1 Then Throw + End Try + Await retryWhen().ConfigureAwait(False) + Next + Return Nothing + End Function + ' + + ' + Public Async Function NeedOnlyOne(Of T)( + ParamArray functions As Func(Of CancellationToken, Task(Of T))()) As Task(Of T) + Dim cts As New CancellationTokenSource() + Dim tasks As Task(Of T)() = (From func In functions Select func(cts.Token)).ToArray() + Dim completed As Task(Of T) = Await Task.WhenAny(tasks).ConfigureAwait(False) + cts.Cancel() + For Each task In tasks + Dim ignored = task.ContinueWith( + Sub(tsk) Stubs.Log(tsk), TaskContinuationOptions.OnlyOnFaulted) + Next + Return Await completed + End Function + ' + + ' + Public Function Interleaved(Of T)(tasks As IEnumerable(Of Task(Of T))) As IEnumerable(Of Task(Of T)) + Dim inputTasks As List(Of Task(Of T)) = tasks.ToList() + Dim sources As List(Of TaskCompletionSource(Of T)) = + (From _i In Enumerable.Range(0, inputTasks.Count) Select New TaskCompletionSource(Of T)()).ToList() + Dim indexRef As Integer() = {-1} + For Each inputTask In inputTasks + inputTask.ContinueWith(Sub(completed) + Dim idx = Interlocked.Increment(indexRef(0)) + Dim source = sources(idx) + If completed.IsFaulted Then + source.TrySetException(completed.Exception.InnerExceptions) + ElseIf completed.IsCanceled Then + source.TrySetCanceled() + Else + source.TrySetResult(completed.Result) + End If + End Sub, + CancellationToken.None, + TaskContinuationOptions.ExecuteSynchronously, + TaskScheduler.Default) + Next + Return From source In sources Select source.Task + End Function + ' + + ' + Public Function WhenAllOrFirstException(Of T)(tasks As IEnumerable(Of Task(Of T))) As Task(Of T()) + Dim inputs As List(Of Task(Of T)) = tasks.ToList() + Dim ce As New CountdownEvent(inputs.Count) + Dim tcs As New TaskCompletionSource(Of T())() + + Dim onCompleted As Action(Of Task) = Sub(completed As Task) + If completed.IsFaulted Then + tcs.TrySetException(completed.Exception.InnerExceptions) + End If + If ce.Signal() AndAlso Not tcs.Task.IsCompleted Then + tcs.TrySetResult(inputs.Select(Function(taskItem) DirectCast(taskItem, Task(Of T)).Result).ToArray()) + End If + End Sub + + For Each t In inputs + t.ContinueWith(onCompleted) + Next + Return tcs.Task + End Function + ' + + ' ---- AsyncCache usage ---- + ' + Private m_webPages As New AsyncCache(Of String, String)(Function(url) Stubs.DownloadStringTaskAsync(url)) + + Public Async Function UseWebPageCache(url As String) As Task + Dim contents As String = Await m_webPages(url) + Console.WriteLine(contents.Length) + End Function + ' + + ' ---- AsyncProducerConsumerCollection usage ---- + ' + Private m_data As New AsyncProducerConsumerCollection(Of Integer)() + + Public Async Function ConsumerAsync() As Task + While True + Dim nextItem As Integer = Await m_data.Take() + Stubs.ProcessNextItem(nextItem) + End While + End Function + + Public Sub Produce(data As Integer) + m_data.Add(data) + End Sub + ' + + ' + Private m_dataBlock As New BufferBlock(Of Integer)() + + Public Async Function ConsumerAsyncBlock() As Task + While True + Dim nextItem As Integer = Await m_dataBlock.ReceiveAsync() + Stubs.ProcessNextItem(nextItem) + End While + End Function + + Public Sub ProduceBlock(data As Integer) + m_dataBlock.Post(data) + End Sub + ' + + ' ---- Cancellation examples ---- + ' + Public Async Function CancelSingleDownload(url As String) As Task + Dim cts As New CancellationTokenSource() + Dim result As String = Await Stubs.DownloadStringTaskAsync(url, cts.Token) + ' at some point later, potentially on another thread + cts.Cancel() + Console.WriteLine(result) + End Function + ' + + ' + Public Async Function CancelMultipleDownloads(urlList As String()) As Task + Dim cts As New CancellationTokenSource() + Dim results As IList(Of String) = Await Task.WhenAll( + From url In urlList Select Stubs.DownloadStringTaskAsync(url, cts.Token)) + ' at some point later, potentially on another thread + cts.Cancel() + Console.WriteLine(results.Count) + End Function + ' + + ' + Public Async Function CancelSubsetDownloads(url As String, outputPath As String) As Task + Dim cts As New CancellationTokenSource() + Dim data As Byte() = Await Stubs.DownloadDataAsync(url, cts.Token) + Await Stubs.SaveToDiskAsync(outputPath, data, CancellationToken.None) + ' at some point later, potentially on another thread + cts.Cancel() + End Function + ' + +End Module + +' ---- AsyncCache ---- +' +Public Class AsyncCache(Of TKey, TValue) + Private ReadOnly _valueFactory As Func(Of TKey, Task(Of TValue)) + Private ReadOnly _map As New ConcurrentDictionary(Of TKey, Lazy(Of Task(Of TValue)))() + + Public Sub New(valueFactory As Func(Of TKey, Task(Of TValue))) + If valueFactory Is Nothing Then Throw New ArgumentNullException(NameOf(valueFactory)) + _valueFactory = valueFactory + End Sub + + Default Public ReadOnly Property Item(key As TKey) As Task(Of TValue) + Get + If key Is Nothing Then Throw New ArgumentNullException(NameOf(key)) + Return _map.GetOrAdd(key, Function(toAdd) New Lazy(Of Task(Of TValue))(Function() _valueFactory(toAdd))).Value + End Get + End Property +End Class +' + +' ---- AsyncProducerConsumerCollection ---- +' +Public Class AsyncProducerConsumerCollection(Of T) + Private ReadOnly m_collection As New Queue(Of T)() + Private ReadOnly m_waiting As New Queue(Of TaskCompletionSource(Of T))() + + Public Sub Add(item As T) + Dim tcs As TaskCompletionSource(Of T) = Nothing + SyncLock m_collection + If m_waiting.Count > 0 Then + tcs = m_waiting.Dequeue() + Else + m_collection.Enqueue(item) + End If + End SyncLock + If tcs IsNot Nothing Then tcs.TrySetResult(item) + End Sub + + Public Function Take() As Task(Of T) + SyncLock m_collection + If m_collection.Count > 0 Then + Return Task.FromResult(m_collection.Dequeue()) + Else + Dim tcs As New TaskCompletionSource(Of T)() + m_waiting.Enqueue(tcs) + Return tcs.Task + End If + End SyncLock + End Function +End Class +' + +' +Class EarlyBailoutUI + Private m_cts As CancellationTokenSource + + Public Sub btnCancel_Click(sender As Object, e As EventArgs) + If m_cts IsNot Nothing Then m_cts.Cancel() + End Sub + + Public Async Sub btnRun_Click(sender As Object, e As EventArgs) + m_cts = New CancellationTokenSource() + Try + Dim imageDownload As Task(Of Bitmap) = Stubs.GetBitmapAsync("url") + Await Examples.UntilCompletionOrCancellation(imageDownload, m_cts.Token) + If imageDownload.IsCompleted Then + Dim image As Bitmap = Await imageDownload + Stubs.Log(image) + Else + imageDownload.ContinueWith(Sub(t) Stubs.Log(t)) + End If + Finally + End Try + End Sub +End Class +' + +' +Class EarlyBailoutWithTokenUI + Private m_cts As CancellationTokenSource + + Public Async Sub btnRun_Click(sender As Object, e As EventArgs) + m_cts = New CancellationTokenSource() + Try + Dim imageDownload As Task(Of Bitmap) = Stubs.GetBitmapAsync("url", m_cts.Token) + Await Examples.UntilCompletionOrCancellation(imageDownload, m_cts.Token) + Dim image As Bitmap = Await imageDownload + Stubs.Log(image) + Catch ex As OperationCanceledException + Finally + End Try + End Sub +End Class +' + +Module Program + Sub Main() + Console.WriteLine("Done.") + End Sub +End Module + diff --git a/docs/standard/asynchronous-programming-patterns/snippets/consuming-the-task-based-asynchronous-pattern/vb/consuming-the-task-based-asynchronous-pattern.vbproj b/docs/standard/asynchronous-programming-patterns/snippets/consuming-the-task-based-asynchronous-pattern/vb/consuming-the-task-based-asynchronous-pattern.vbproj new file mode 100644 index 0000000000000..f631a8368d38b --- /dev/null +++ b/docs/standard/asynchronous-programming-patterns/snippets/consuming-the-task-based-asynchronous-pattern/vb/consuming-the-task-based-asynchronous-pattern.vbproj @@ -0,0 +1,15 @@ + + + + Exe + ConsumingTAP + net10.0 + CA1416 + + + + + + + + diff --git a/docs/standard/asynchronous-programming-patterns/snippets/implementing-the-task-based-asynchronous-pattern/csharp/Program.cs b/docs/standard/asynchronous-programming-patterns/snippets/implementing-the-task-based-asynchronous-pattern/csharp/Program.cs new file mode 100644 index 0000000000000..f2611a74f4f92 --- /dev/null +++ b/docs/standard/asynchronous-programming-patterns/snippets/implementing-the-task-based-asynchronous-pattern/csharp/Program.cs @@ -0,0 +1,146 @@ +using System.Drawing; + +// +static class StreamExtensions +{ + public static Task ReadTask(this Stream stream, byte[] buffer, int offset, int count, object? state) + { + var tcs = new TaskCompletionSource(); + stream.BeginRead(buffer, offset, count, ar => + { + try { tcs.SetResult(stream.EndRead(ar)); } + catch (Exception exc) { tcs.SetException(exc); } + }, state); + return tcs.Task; + } +} +// + +// +class Calculator +{ + private int value = 0; + + public Task MethodAsync(string input) + { + if (input == null) throw new ArgumentNullException(nameof(input)); + return MethodAsyncInternal(input); + } + + private async Task MethodAsyncInternal(string input) + { + // code that uses await goes here + await Task.Delay(1); + return value; + } +} +// + +internal class ImageData +{ + public int Width { get; set; } = 1; + public int Height { get; set; } = 1; +} + +static class ImageRenderer +{ + // + internal static Task RenderAsync(ImageData data, CancellationToken cancellationToken) + { + return Task.Run(() => + { + var bmp = new Bitmap(data.Width, data.Height); + for (int y = 0; y < data.Height; y++) + { + cancellationToken.ThrowIfCancellationRequested(); + for (int x = 0; x < data.Width; x++) + { + // render pixel [x,y] into bmp + } + } + return bmp; + }, cancellationToken); + } + // + + // + public static async Task DownloadDataAndRenderImageAsync(CancellationToken cancellationToken) + { + var imageData = await DownloadImageDataAsync(cancellationToken); + return await RenderAsync(imageData, cancellationToken); + } + // + + private static Task DownloadImageDataAsync(CancellationToken ct) + { + return Task.FromResult(new ImageData()); + } +} + +static class TimerExamples +{ + // + public static Task Delay(int millisecondsTimeout) + { + TaskCompletionSource? tcs = null; + Timer? timer = null; + + timer = new Timer(delegate + { + timer!.Dispose(); + tcs!.TrySetResult(DateTimeOffset.UtcNow); + }, null, Timeout.Infinite, Timeout.Infinite); + + tcs = new TaskCompletionSource(timer); + timer.Change(millisecondsTimeout, Timeout.Infinite); + return tcs.Task; + } + // + + // + public static Task DelaySimple(int millisecondsTimeout) + { + TaskCompletionSource? tcs = null; + Timer? timer = null; + + timer = new Timer(delegate + { + timer!.Dispose(); + tcs!.TrySetResult(true); + }, null, Timeout.Infinite, Timeout.Infinite); + + tcs = new TaskCompletionSource(timer); + timer.Change(millisecondsTimeout, Timeout.Infinite); + return tcs.Task; + } + // + + // + public static async Task Poll(Uri url, CancellationToken cancellationToken, IProgress progress) + { + while (true) + { + await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken); + bool success = false; + try + { + await DownloadStringAsync(url); + success = true; + } + catch { /* ignore errors */ } + progress.Report(success); + } + } + // + + private static Task DownloadStringAsync(Uri url) => Task.FromResult(string.Empty); +} + +static class Program +{ + static async Task Main() + { + var bmp = await ImageRenderer.DownloadDataAndRenderImageAsync(CancellationToken.None); + Console.WriteLine($"Rendered {bmp.Width}x{bmp.Height} image."); + } +} diff --git a/docs/standard/asynchronous-programming-patterns/snippets/implementing-the-task-based-asynchronous-pattern/csharp/implementing-the-task-based-asynchronous-pattern.csproj b/docs/standard/asynchronous-programming-patterns/snippets/implementing-the-task-based-asynchronous-pattern/csharp/implementing-the-task-based-asynchronous-pattern.csproj new file mode 100644 index 0000000000000..ca2de5eb3c757 --- /dev/null +++ b/docs/standard/asynchronous-programming-patterns/snippets/implementing-the-task-based-asynchronous-pattern/csharp/implementing-the-task-based-asynchronous-pattern.csproj @@ -0,0 +1,15 @@ + + + + Exe + net10.0 + enable + enable + CA1416 + + + + + + + diff --git a/samples/snippets/visualbasic/VS_Snippets_CLR/conceptual.tap_patterns/vb/patterns1.vb b/docs/standard/asynchronous-programming-patterns/snippets/implementing-the-task-based-asynchronous-pattern/vb/Program.vb similarity index 63% rename from samples/snippets/visualbasic/VS_Snippets_CLR/conceptual.tap_patterns/vb/patterns1.vb rename to docs/standard/asynchronous-programming-patterns/snippets/implementing-the-task-based-asynchronous-pattern/vb/Program.vb index 62eefcccee2c6..16d4ce4cf606d 100644 --- a/samples/snippets/visualbasic/VS_Snippets_CLR/conceptual.tap_patterns/vb/patterns1.vb +++ b/docs/standard/asynchronous-programming-patterns/snippets/implementing-the-task-based-asynchronous-pattern/vb/Program.vb @@ -1,53 +1,58 @@ -' Visual Basic .NET Document -Option Strict On - -Imports System.IO +Imports System Imports System.Drawing +Imports System.IO Imports System.Runtime.CompilerServices Imports System.Threading Imports System.Threading.Tasks -Module Example - ' +' +Module StreamExtensions - Public Function ReadTask(stream As Stream, buffer() As Byte, + Public Function ReadTask(stream As Stream, buffer As Byte(), offset As Integer, count As Integer, state As Object) As Task(Of Integer) Dim tcs As New TaskCompletionSource(Of Integer)() - stream.BeginRead(buffer, offset, count, Sub(ar) - Try - tcs.SetResult(stream.EndRead(ar)) - Catch exc As Exception - tcs.SetException(exc) - End Try - End Sub, state) + stream.BeginRead(buffer, offset, count, + Sub(ar) + Try + tcs.SetResult(stream.EndRead(ar)) + Catch exc As Exception + tcs.SetException(exc) + End Try + End Sub, state) Return tcs.Task End Function - ' +End Module +' - Dim value As Integer = 0 +' +Class Calculator + Private value As Integer = 0 - ' Public Function MethodAsync(input As String) As Task(Of Integer) - If input Is Nothing Then Throw New ArgumentNullException("input") - + If input Is Nothing Then Throw New ArgumentNullException(NameOf(input)) Return MethodAsyncInternal(input) End Function Private Async Function MethodAsyncInternal(input As String) As Task(Of Integer) - ' code that uses await goes here - - return value + Await Task.Delay(1) + Return value End Function - ' +End Class +' + +Friend Class ImageData + Public Property Width As Integer = 1 + Public Property Height As Integer = 1 +End Class - ' - Friend Function RenderAsync(data As ImageData, cancellationToken As _ - CancellationToken) As Task(Of Bitmap) +Module ImageRenderer + ' + Friend Function RenderAsync(data As ImageData, cancellationToken As CancellationToken) As Task(Of Bitmap) Return Task.Run(Function() Dim bmp As New Bitmap(data.Width, data.Height) - For y As Integer = 0 to data.Height - 1 + For y As Integer = 0 To data.Height - 1 cancellationToken.ThrowIfCancellationRequested() For x As Integer = 0 To data.Width - 1 ' render pixel [x,y] into bmp @@ -56,9 +61,22 @@ Module Example Return bmp End Function, cancellationToken) End Function - ' + ' + + ' + Public Async Function DownloadDataAndRenderImageAsync(cancellationToken As CancellationToken) As Task(Of Bitmap) + Dim imageData As ImageData = Await DownloadImageDataAsync(cancellationToken) + Return Await RenderAsync(imageData, cancellationToken) + End Function + ' - ' + Private Function DownloadImageDataAsync(ct As CancellationToken) As Task(Of ImageData) + Return Task.FromResult(New ImageData()) + End Function +End Module + +Module TimerExamples + ' Public Function Delay(millisecondsTimeout As Integer) As Task(Of DateTimeOffset) Dim tcs As TaskCompletionSource(Of DateTimeOffset) = Nothing Dim timer As Timer = Nothing @@ -72,63 +90,50 @@ Module Example timer.Change(millisecondsTimeout, Timeout.Infinite) Return tcs.Task End Function - ' + ' + + ' + Public Function DelaySimple(millisecondsTimeout As Integer) As Task(Of Boolean) + Dim tcs As TaskCompletionSource(Of Boolean) = Nothing + Dim timer As Timer = Nothing + + timer = New Timer(Sub(obj) + timer.Dispose() + tcs.TrySetResult(True) + End Sub, Nothing, Timeout.Infinite, Timeout.Infinite) - ' + tcs = New TaskCompletionSource(Of Boolean)(timer) + timer.Change(millisecondsTimeout, Timeout.Infinite) + Return tcs.Task + End Function + ' + + ' Public Async Function Poll(url As Uri, cancellationToken As CancellationToken, progress As IProgress(Of Boolean)) As Task Do While True Await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken) Dim success As Boolean = False Try - await DownloadStringAsync(url) - success = true + Await DownloadStringAsync(url) + success = True Catch ' ignore errors End Try progress.Report(success) Loop End Function - ' + ' Private Function DownloadStringAsync(url As Uri) As Task(Of String) - Dim tcs As New TaskCompletionSource(Of String)() - Return tcs.Task + Return Task.FromResult(String.Empty) End Function End Module -Public Module Pattern2 - ' - Public Function Delay(millisecondsTimeout As Integer) As Task(Of Boolean) - Dim tcs As TaskCompletionSource(Of Boolean) = Nothing - Dim timer As Timer = Nothing - - Timer = new Timer(Sub(obj) - timer.Dispose() - tcs.TrySetResult(True) - End Sub, Nothing, Timeout.Infinite, Timeout.Infinite) - - tcs = New TaskCompletionSource(Of Boolean)(timer) - timer.Change(millisecondsTimeout, Timeout.Infinite) - Return tcs.Task - End Function - ' - - ' - Public Async Function DownloadDataAndRenderImageAsync( - cancellationToken As CancellationToken) As Task(Of Bitmap) - Dim imageData As ImageData = Await DownloadImageDataAsync(cancellationToken) - Return Await RenderAsync(imageData, cancellationToken) - End Function - ' - - Private Async Function DownloadImageDataAsync(c As CancellationToken) As Task(Of ImageData) - ' return new TaskCompletionSource().Task; - Return New ImageData() - End Function +Module Program + Sub Main() + Dim bmp As Bitmap = ImageRenderer.DownloadDataAndRenderImageAsync(CancellationToken.None).GetAwaiter().GetResult() + Console.WriteLine($"Rendered {bmp.Width}x{bmp.Height} image.") + End Sub End Module -Friend Class ImageData - Public Width As Integer = 0 - Public Height As Integer = 0 -End Class diff --git a/docs/standard/asynchronous-programming-patterns/snippets/implementing-the-task-based-asynchronous-pattern/vb/implementing-the-task-based-asynchronous-pattern.vbproj b/docs/standard/asynchronous-programming-patterns/snippets/implementing-the-task-based-asynchronous-pattern/vb/implementing-the-task-based-asynchronous-pattern.vbproj new file mode 100644 index 0000000000000..9a9fd483212f4 --- /dev/null +++ b/docs/standard/asynchronous-programming-patterns/snippets/implementing-the-task-based-asynchronous-pattern/vb/implementing-the-task-based-asynchronous-pattern.vbproj @@ -0,0 +1,15 @@ + + + + Exe + ImplementingTAP + net10.0 + ImplementingTAP.Program + CA1416 + + + + + + + diff --git a/docs/standard/asynchronous-programming-patterns/snippets/keeping-async-methods-alive/csharp/KeepingAsyncMethodsAlive.csproj b/docs/standard/asynchronous-programming-patterns/snippets/keeping-async-methods-alive/csharp/KeepingAsyncMethodsAlive.csproj new file mode 100644 index 0000000000000..dfb40caafcf9a --- /dev/null +++ b/docs/standard/asynchronous-programming-patterns/snippets/keeping-async-methods-alive/csharp/KeepingAsyncMethodsAlive.csproj @@ -0,0 +1,10 @@ + + + + Exe + net10.0 + enable + enable + + + diff --git a/docs/standard/asynchronous-programming-patterns/snippets/keeping-async-methods-alive/csharp/Program.cs b/docs/standard/asynchronous-programming-patterns/snippets/keeping-async-methods-alive/csharp/Program.cs new file mode 100644 index 0000000000000..3b75122ad7531 --- /dev/null +++ b/docs/standard/asynchronous-programming-patterns/snippets/keeping-async-methods-alive/csharp/Program.cs @@ -0,0 +1,85 @@ +using System.Collections.Concurrent; + +// +public static class FireAndForgetPitfall +{ + public static async Task RunAsync() + { + _ = Task.Run(async () => + { + await Task.Delay(100); + throw new InvalidOperationException("Background operation failed."); + }); + + await Task.Delay(150); + Console.WriteLine("Caller finished without observing background completion."); + } +} +// + +public sealed class BackgroundTaskTracker +{ + private readonly ConcurrentDictionary _inFlight = new(); + + public void Track(Task operationTask, string name) + { + int id = operationTask.Id; + _inFlight[id] = operationTask; + + _ = operationTask.ContinueWith(completedTask => + { + _inFlight.TryRemove(id, out _); + + if (completedTask.IsFaulted) + { + Console.WriteLine($"{name} failed: {completedTask.Exception?.GetBaseException().Message}"); + } + }, TaskScheduler.Default); + } + + public Task DrainAsync() + { + Task[] snapshot = _inFlight.Values.ToArray(); + return snapshot.Length == 0 ? Task.CompletedTask : Task.WhenAll(snapshot); + } +} + +// +public static class FireAndForgetFix +{ + public static async Task RunAsync(BackgroundTaskTracker tracker) + { + Task backgroundTask = Task.Run(async () => + { + await Task.Delay(100); + throw new InvalidOperationException("Background operation failed."); + }); + + tracker.Track(backgroundTask, "Cache refresh"); + + try + { + await tracker.DrainAsync(); + } + catch (Exception ex) + { + Console.WriteLine($"Drain observed failure: {ex.GetBaseException().Message}"); + } + } +} +// + +public static class Program +{ + public static async Task Main() + { + Console.WriteLine("--- Pitfall ---"); + await FireAndForgetPitfall.RunAsync(); + + Console.WriteLine("--- Fix ---"); + var tracker = new BackgroundTaskTracker(); + await FireAndForgetFix.RunAsync(tracker); + + Console.WriteLine("Done."); + } +} diff --git a/docs/standard/asynchronous-programming-patterns/snippets/keeping-async-methods-alive/vb/KeepingAsyncMethodsAlive.vbproj b/docs/standard/asynchronous-programming-patterns/snippets/keeping-async-methods-alive/vb/KeepingAsyncMethodsAlive.vbproj new file mode 100644 index 0000000000000..c806c56aaac54 --- /dev/null +++ b/docs/standard/asynchronous-programming-patterns/snippets/keeping-async-methods-alive/vb/KeepingAsyncMethodsAlive.vbproj @@ -0,0 +1,9 @@ + + + + Exe + KeepingAsyncMethodsAlive + net10.0 + + + diff --git a/docs/standard/asynchronous-programming-patterns/snippets/keeping-async-methods-alive/vb/Program.vb b/docs/standard/asynchronous-programming-patterns/snippets/keeping-async-methods-alive/vb/Program.vb new file mode 100644 index 0000000000000..5b783d67187fc --- /dev/null +++ b/docs/standard/asynchronous-programming-patterns/snippets/keeping-async-methods-alive/vb/Program.vb @@ -0,0 +1,76 @@ +Imports System.Collections.Concurrent + +' +Public Module FireAndForgetPitfall + Public Async Function RunAsync() As Task + Dim discardedTask As Task = Task.Run(Async Function() + Await Task.Delay(100) + Throw New InvalidOperationException("Background operation failed.") + End Function) + + Await Task.Delay(150) + Console.WriteLine("Caller finished without observing background completion.") + End Function +End Module +' + +Public NotInheritable Class BackgroundTaskTracker + Private ReadOnly _inFlight As New ConcurrentDictionary(Of Integer, Task)() + + Public Sub Track(operationTask As Task, name As String) + Dim id As Integer = operationTask.Id + _inFlight(id) = operationTask + + Dim continuationTask As Task = operationTask.ContinueWith(Sub(completedTask) + Dim removedTask As Task = Nothing + _inFlight.TryRemove(id, removedTask) + + If completedTask.IsFaulted Then + Console.WriteLine($"{name} failed: {completedTask.Exception.GetBaseException().Message}") + End If + End Sub, + TaskScheduler.Default) + End Sub + + Public Function DrainAsync() As Task + Dim snapshot As Task() = _inFlight.Values.ToArray() + + If snapshot.Length = 0 Then + Return Task.CompletedTask + End If + + Return Task.WhenAll(snapshot) + End Function +End Class + +' +Public Module FireAndForgetFix + Public Async Function RunAsync(tracker As BackgroundTaskTracker) As Task + Dim backgroundTask As Task = Task.Run(Async Function() + Await Task.Delay(100) + Throw New InvalidOperationException("Background operation failed.") + End Function) + + tracker.Track(backgroundTask, "Cache refresh") + + Try + Await tracker.DrainAsync() + Catch ex As Exception + Console.WriteLine($"Drain observed failure: {ex.GetBaseException().Message}") + End Try + End Function +End Module +' + +Module Program + Sub Main() + Console.WriteLine("--- Pitfall ---") + FireAndForgetPitfall.RunAsync().Wait() + + Console.WriteLine("--- Fix ---") + Dim tracker As New BackgroundTaskTracker() + FireAndForgetFix.RunAsync(tracker).Wait() + + Console.WriteLine("Done.") + End Sub +End Module diff --git a/docs/standard/asynchronous-programming-patterns/snippets/task-based-asynchronous-pattern-tap/csharp/Program.cs b/docs/standard/asynchronous-programming-patterns/snippets/task-based-asynchronous-pattern-tap/csharp/Program.cs new file mode 100644 index 0000000000000..cce6b76e56dd5 --- /dev/null +++ b/docs/standard/asynchronous-programming-patterns/snippets/task-based-asynchronous-pattern-tap/csharp/Program.cs @@ -0,0 +1,42 @@ +using System.Collections.ObjectModel; + +public class FindFilesProgressInfo { } + +public class Examples +{ + // + public static Task ReadAsync(byte[] buffer, int offset, int count, + CancellationToken cancellationToken) + // + { + return Task.Run(() => Thread.Sleep(1), cancellationToken); + } + + // + public static Task ReadAsync(byte[] buffer, int offset, int count, + IProgress progress) + // + { + return Task.Run(() => Thread.Sleep(1)); + } + + // + public static Task> FindFilesAsync( + string pattern, + IProgress>>> progress) + // + { + return Task.FromResult(new ReadOnlyCollection(Array.Empty())); + } + + // + public static Task> FindFilesAsync( + string pattern, + IProgress progress) + // + { + return Task.FromResult(new ReadOnlyCollection(Array.Empty())); + } +} + +static class Program { static void Main() { } } diff --git a/docs/standard/asynchronous-programming-patterns/snippets/task-based-asynchronous-pattern-tap/csharp/task-based-asynchronous-pattern-tap.csproj b/docs/standard/asynchronous-programming-patterns/snippets/task-based-asynchronous-pattern-tap/csharp/task-based-asynchronous-pattern-tap.csproj new file mode 100644 index 0000000000000..ed9781c223ab9 --- /dev/null +++ b/docs/standard/asynchronous-programming-patterns/snippets/task-based-asynchronous-pattern-tap/csharp/task-based-asynchronous-pattern-tap.csproj @@ -0,0 +1,10 @@ + + + + Exe + net10.0 + enable + enable + + + diff --git a/docs/standard/asynchronous-programming-patterns/snippets/task-based-asynchronous-pattern-tap/vb/Program.vb b/docs/standard/asynchronous-programming-patterns/snippets/task-based-asynchronous-pattern-tap/vb/Program.vb new file mode 100644 index 0000000000000..2817ea1a88119 --- /dev/null +++ b/docs/standard/asynchronous-programming-patterns/snippets/task-based-asynchronous-pattern-tap/vb/Program.vb @@ -0,0 +1,45 @@ +Imports System +Imports System.Collections.ObjectModel +Imports System.IO +Imports System.Threading +Imports System.Threading.Tasks + +Public Class FindFilesProgressInfo +End Class + +Module Examples + ' + Public Function ReadAsync(buffer As Byte(), offset As Integer, count As Integer, + cancellationToken As CancellationToken) As Task + ' + Return Task.Run(Sub() Thread.Sleep(1), cancellationToken) + End Function + + ' + Public Function ReadAsync(buffer As Byte(), offset As Integer, count As Integer, + progress As IProgress(Of Long)) As Task + ' + Return Task.Run(Sub() Thread.Sleep(1)) + End Function + + ' + Public Function FindFilesAsync( + pattern As String, + progress As IProgress(Of Tuple(Of Double, ReadOnlyCollection(Of List(Of FileInfo))))) As Task(Of ReadOnlyCollection(Of FileInfo)) + ' + Return Task.FromResult(New ReadOnlyCollection(Of FileInfo)(Array.Empty(Of FileInfo)())) + End Function + + ' + Public Function FindFilesAsync( + pattern As String, + progress As IProgress(Of FindFilesProgressInfo)) As Task(Of ReadOnlyCollection(Of FileInfo)) + ' + Return Task.FromResult(New ReadOnlyCollection(Of FileInfo)(Array.Empty(Of FileInfo)())) + End Function +End Module + +Module Program + Sub Main(args As String()) + End Sub +End Module diff --git a/docs/standard/asynchronous-programming-patterns/snippets/task-based-asynchronous-pattern-tap/vb/task-based-asynchronous-pattern-tap.vbproj b/docs/standard/asynchronous-programming-patterns/snippets/task-based-asynchronous-pattern-tap/vb/task-based-asynchronous-pattern-tap.vbproj new file mode 100644 index 0000000000000..e724c875db661 --- /dev/null +++ b/docs/standard/asynchronous-programming-patterns/snippets/task-based-asynchronous-pattern-tap/vb/task-based-asynchronous-pattern-tap.vbproj @@ -0,0 +1,10 @@ + + + + Exe + TapOverview + net10.0 + TapOverview.Program + + + diff --git a/docs/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap.md b/docs/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap.md index 72cb51553d342..fc9417e7a2b33 100644 --- a/docs/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap.md +++ b/docs/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap.md @@ -1,7 +1,7 @@ --- title: "Task-based Asynchronous Pattern (TAP): Introduction and overview" description: "Learn about the Task-based Asynchronous Pattern (TAP), and compare it to the legacy patterns: Asynchronous Programming Model (APM) and Event-based Asynchronous Pattern (EAP)." -ms.date: 10/22/2025 +ms.date: 04/17/2026 dev_langs: - "csharp" - "vb" @@ -14,83 +14,100 @@ ai-usage: ai-assisted --- # Task-based asynchronous pattern (TAP) in .NET: Introduction and overview -In .NET, The task-based asynchronous pattern is the recommended asynchronous design pattern for new development. It is based on the and types in the namespace, which are used to represent asynchronous operations. +In .NET, the task-based asynchronous pattern is the recommended asynchronous design pattern for new development. It's based on the and types in the namespace, which represent asynchronous operations. ## Naming, parameters, and return types -TAP uses a single method to represent the initiation and completion of an asynchronous operation. This contrasts with both the Asynchronous Programming Model (APM or `IAsyncResult`) pattern and the Event-based Asynchronous Pattern (EAP). APM requires `Begin` and `End` methods. EAP requires a method that has the `Async` suffix and also requires one or more events, event handler delegate types, and `EventArg`-derived types. Asynchronous methods in TAP include the `Async` suffix after the operation name for methods that return awaitable types, such as , , , and . For example, an asynchronous `Get` operation that returns a `Task` can be named `GetAsync`. If you're adding a TAP method to a class that already contains an EAP method name with the `Async` suffix, use the suffix `TaskAsync` instead. For example, if the class already has a `GetAsync` method, use the name `GetTaskAsync`. If a method starts an asynchronous operation but doesn't return an awaitable type, its name should start with `Begin`, `Start`, or some other verb to suggest that this method doesn't return or throw the result of the operation.   +TAP uses a single method to represent the initiation and completion of an asynchronous operation. This approach contrasts with both the Asynchronous Programming Model (APM or `IAsyncResult`) pattern and the Event-based Asynchronous Pattern (EAP). APM requires `Begin` and `End` methods. EAP requires a method that has the `Async` suffix and also requires one or more events, event handler delegate types, and `EventArg`-derived types. Asynchronous methods in TAP include the `Async` suffix after the operation name for methods that return awaitable types, such as , , , and . For example, an asynchronous `Get` operation that returns a `Task` can be named `GetAsync`. If you're adding a TAP method to a class that already contains an EAP method name with the `Async` suffix, use the suffix `TaskAsync` instead. For example, if the class already has a `GetAsync` method, use the name `GetTaskAsync`. If a method starts an asynchronous operation but doesn't return an awaitable type, its name should start with `Begin`, `Start`, or some other verb to suggest that this method doesn't return or throw the result of the operation. - A TAP method returns either a or a , based on whether the corresponding synchronous method returns void or a type `TResult`. +A TAP method returns either a or a , based on whether the corresponding synchronous method returns void or a type `TResult`. - The parameters of a TAP method should match the parameters of its synchronous counterpart and should be provided in the same order. However, `out` and `ref` parameters are exempt from this rule and should be avoided entirely. Any data that has been returned through an `out` or `ref` parameter should instead be returned as part of the `TResult` returned by , and should use a tuple or a custom data structure to accommodate multiple values. Also, consider adding a parameter even if the TAP method's synchronous counterpart doesn't offer one. +The parameters of a TAP method should match the parameters of its synchronous counterpart and should be provided in the same order. However, `out` and `ref` parameters are exempt from this rule and should be avoided entirely. Any data that an `out` or `ref` parameter returns should instead be part of the `TResult` returned by , and should use a tuple or a custom data structure to accommodate multiple values. Also, consider adding a parameter even if the TAP method's synchronous counterpart doesn't offer one. - Methods that are devoted exclusively to the creation, manipulation, or combination of tasks (where the asynchronous intent of the method is clear in the method name or in the name of the type to which the method belongs) need not follow this naming pattern; such methods are often referred to as *combinators*. Examples of combinators include and , and are discussed in the [Using the Built-in Task-based Combinators](consuming-the-task-based-asynchronous-pattern.md#combinators) section of the article [Consuming the Task-based Asynchronous Pattern](consuming-the-task-based-asynchronous-pattern.md). +Methods that are devoted exclusively to the creation, manipulation, or combination of tasks (where the asynchronous intent of the method is clear in the method name or in the name of the type to which the method belongs) need not follow this naming pattern. Such methods are often referred to as *combinators*. Examples of combinators include and , and are discussed in the [Using the Built-in Task-based Combinators](consuming-the-task-based-asynchronous-pattern.md#combinators) section of the article [Consuming the Task-based Asynchronous Pattern](consuming-the-task-based-asynchronous-pattern.md). - For examples of how the TAP syntax differs from the syntax used in legacy asynchronous programming patterns such as the Asynchronous Programming Model (APM) and the Event-based Asynchronous Pattern (EAP), see [Asynchronous Programming Patterns](index.md). +For examples of how the TAP syntax differs from the syntax used in legacy asynchronous programming patterns such as the Asynchronous Programming Model (APM) and the Event-based Asynchronous Pattern (EAP), see [Asynchronous Programming Patterns](index.md). -## Initiating an asynchronous operation +### Async behavior, return types, and naming + +The `async` keyword doesn't force a method to run asynchronously on another thread. It enables `await`, and the method runs synchronously until it reaches an incomplete awaitable. If the method doesn't reach an incomplete awaitable, it can complete synchronously. + +For most APIs, prefer these return types: + +- Use for asynchronous operations that don't produce a value. +- Use for asynchronous operations that produce a value. +- Use or only when measurements show allocation pressure and when consumers can handle the extra usage constraints. - An asynchronous method that is based on TAP can do a small amount of work synchronously, such as validating arguments and initiating the asynchronous operation, before it returns the resulting task. Synchronous work should be kept to the minimum so the asynchronous method can return quickly. Reasons for a quick return include: +Keep TAP naming predictable: -- Asynchronous methods may be invoked from user interface (UI) threads, and any long-running synchronous work could harm the responsiveness of the application. +- Use the `Async` suffix for methods that return awaitable types. +- Don't append `Async` to synchronous methods. +- Keep existing public names stable, and add new TAP overloads instead of renaming established APIs unless you're already making a breaking change. -- Multiple asynchronous methods may be launched concurrently. Therefore, any long-running work in the synchronous portion of an asynchronous method could delay the initiation of other asynchronous operations, thereby decreasing the benefits of concurrency. +## Initiating an asynchronous operation + +An asynchronous method that is based on TAP can do a small amount of work synchronously, such as validating arguments and initiating the asynchronous operation, before it returns the resulting task. Keep synchronous work to a minimum so the asynchronous method can return quickly. Reasons for a quick return include: - In some cases, the amount of work required to complete the operation is less than the amount of work required to launch the operation asynchronously. Reading from a stream where the read operation can be satisfied by data that is already buffered in memory is an example of such a scenario. In such cases, the operation may complete synchronously, and may return a task that has already been completed. +- You might invoke asynchronous methods from user interface (UI) threads, and any long-running synchronous work could harm the responsiveness of the application. +- You might launch multiple asynchronous methods concurrently. Therefore, any long-running work in the synchronous portion of an asynchronous method could delay the initiation of other asynchronous operations, thereby decreasing the benefits of concurrency. + +In some cases, the amount of work required to complete the operation is less than the amount of work required to launch the operation asynchronously. Reading from a stream where the read operation can be satisfied by data that's already buffered in memory is an example of such a scenario. In such cases, the operation might complete synchronously, and might return a task that is already completed. ## Exceptions - An asynchronous method should raise an exception to be thrown out of the asynchronous method call only in response to a usage error. Usage errors should never occur in production code. For example, if passing a null reference (`Nothing` in Visual Basic) as one of the method's arguments causes an error state (usually represented by an exception), you can modify the calling code to ensure that a null reference is never passed. For all other errors, exceptions that occur when an asynchronous method is running should be assigned to the returned task, even if the asynchronous method happens to complete synchronously before the task is returned. Typically, a task contains at most one exception. However, if the task represents multiple operations (for example, ), multiple exceptions may be associated with a single task. +An asynchronous method should raise an exception to throw out of the asynchronous method call only in response to a usage error. Usage errors should never occur in production code. For example, if passing a null reference (`Nothing` in Visual Basic) as one of the method's arguments causes an error state (usually represented by an exception), you can modify the calling code to ensure that a null reference is never passed. For all other errors, assign exceptions that occur when an asynchronous method is running to the returned task, even if the asynchronous method happens to complete synchronously before the task is returned. Typically, a task contains at most one exception. However, if the task represents multiple operations (for example, ), multiple exceptions might be associated with a single task. ## Target environment - When you implement a TAP method, you can determine where asynchronous execution occurs. You may choose to execute the workload on the thread pool, implement it by using asynchronous I/O (without being bound to a thread for the majority of the operation's execution), run it on a specific thread (such as the UI thread), or use any number of potential contexts. A TAP method may even have nothing to execute, and may just return a that represents the occurrence of a condition elsewhere in the system (for example, a task that represents data arriving at a queued data structure). +When you implement a TAP method, you can determine where asynchronous execution occurs. You might choose to execute the workload on the thread pool, implement it by using asynchronous I/O (without being bound to a thread for the majority of the operation's execution), run it on a specific thread (such as the UI thread), or use any number of potential contexts. A TAP method might even have nothing to execute, and might just return a that represents the occurrence of a condition elsewhere in the system (for example, a task that represents data arriving at a queued data structure). - The caller of the TAP method may block waiting for the TAP method to complete by synchronously waiting on the resulting task, or may run additional (continuation) code when the asynchronous operation completes. The creator of the continuation code has control over where that code executes. You may create the continuation code either explicitly, through methods on the class (for example, ) or implicitly, by using language support built on top of continuations (for example, `await` in C#, `Await` in Visual Basic, `AwaitValue` in F#). +The caller of the TAP method can block waiting for the TAP method to complete by synchronously waiting on the resulting task, or can run additional (continuation) code when the asynchronous operation completes. The creator of the continuation code has control over where that code executes. You can create the continuation code either explicitly, through methods on the class (for example, ), or implicitly, by using language support built on top of continuations (for example, `await` in C#, `Await` in Visual Basic, `AwaitValue` in F#). ## Task status - The class provides a life cycle for asynchronous operations, and that cycle is represented by the enumeration. To support corner cases of types that derive from and , and to support the separation of construction from scheduling, the class exposes a method. Tasks that are created by the public constructors are referred to as *cold tasks*, because they begin their life cycle in the non-scheduled state and are scheduled only when is called on these instances. +The class provides a life cycle for asynchronous operations, and that cycle is represented by the enumeration. To support corner cases of types that derive from and , and to support the separation of construction from scheduling, the class exposes a method. Tasks that are created by the public constructors are referred to as *cold tasks*, because they begin their life cycle in the non-scheduled state and are scheduled only when is called on these instances. + +All other tasks begin their life cycle in a hot state, which means that the asynchronous operations they represent are already initiated and their task status is an enumeration value other than . All tasks that are returned from TAP methods must be activated. **If a TAP method internally uses a task's constructor to instantiate the task to be returned, the TAP method must call on the object before returning it.** Consumers of a TAP method can safely assume that the returned task is active and shouldn't try to call on any that is returned from a TAP method. Calling on an active task results in an exception. - All other tasks begin their life cycle in a hot state, which means that the asynchronous operations they represent have already been initiated and their task status is an enumeration value other than . All tasks that are returned from TAP methods must be activated. **If a TAP method internally uses a task's constructor to instantiate the task to be returned, the TAP method must call on the object before returning it.** Consumers of a TAP method may safely assume that the returned task is active and shouldn't try to call on any that is returned from a TAP method. Calling on an active task results in an exception. +For guidance on fire-and-forget lifetime and ownership concerns after task activation, see [Keeping async methods alive](keeping-async-methods-alive.md). ## Cancellation (optional) - In TAP, cancellation is optional for both asynchronous method implementers and asynchronous method consumers. If an operation allows cancellation, it exposes an overload of the asynchronous method that accepts a cancellation token ( instance). By convention, the parameter is named `cancellationToken`. +In TAP, cancellation is optional for both asynchronous method implementers and asynchronous method consumers. If an operation allows cancellation, it exposes an overload of the asynchronous method that accepts a cancellation token ( instance). By convention, the parameter is named `cancellationToken`. - [!code-csharp[Conceptual.TAP#1](../../../samples/snippets/csharp/VS_Snippets_CLR/conceptual.tap/cs/examples1.cs#1)] - [!code-vb[Conceptual.TAP#1](../../../samples/snippets/visualbasic/VS_Snippets_CLR/conceptual.tap/vb/examples1.vb#1)] +:::code language="csharp" source="./snippets/task-based-asynchronous-pattern-tap/csharp/Program.cs" id="CancellationParameter"::: +:::code language="vb" source="./snippets/task-based-asynchronous-pattern-tap/vb/Program.vb" id="CancellationParameter"::: - The asynchronous operation monitors this token for cancellation requests. If it receives a cancellation request, it may choose to honor that request and cancel the operation. If the cancellation request results in work being ended prematurely, the TAP method returns a task that ends in the state; there is no available result and no exception is thrown. The state is considered to be a final (completed) state for a task, along with the and states. Therefore, if a task is in the state, its property returns `true`. When a task completes in the state, any continuations registered with the task are scheduled or executed, unless a continuation option such as was specified to opt out of continuation. Any code that is asynchronously waiting for a canceled task through use of language features continues to run but receives an or an exception derived from it. Code that is blocked synchronously waiting on the task through methods such as and also continue to run with an exception. +The asynchronous operation monitors this token for cancellation requests. If it receives a cancellation request, it might choose to honor that request and cancel the operation. If the cancellation request results in work ending prematurely, the TAP method returns a task that ends in the state; there's no available result and no exception is thrown. The state is considered to be a final (completed) state for a task, along with the and states. Therefore, if a task is in the state, its property returns `true`. When a task completes in the state, any continuations registered with the task are scheduled or executed, unless a continuation option such as was specified to opt out of continuation. Any code that is asynchronously waiting for a canceled task through use of language features continues to run but receives an or an exception derived from it. Code that is blocked synchronously waiting on the task through methods such as and also continue to run with an exception. - If a cancellation token has requested cancellation before the TAP method that accepts that token is called, the TAP method should return a task. However, if cancellation is requested while the asynchronous operation is running, the asynchronous operation need not accept the cancellation request. The returned task should end in the state only if the operation ends as a result of the cancellation request. If cancellation is requested but a result or an exception is still produced, the task should end in the or state. +If a cancellation token requests cancellation before the TAP method that accepts that token is called, the TAP method should return a task. However, if cancellation is requested while the asynchronous operation is running, the asynchronous operation need not accept the cancellation request. The returned task should end in the state only if the operation ends as a result of the cancellation request. If cancellation is requested but a result or an exception is still produced, the task should end in the or state. - For asynchronous methods that want to expose the ability to be canceled first and foremost, you don't have to provide an overload that doesn't accept a cancellation token. For methods that can't be canceled, don't provide overloads that accept a cancellation token; this helps indicate to the caller whether the target method is actually cancelable. Consumer code that doesn't desire cancellation may call a method that accepts a and provide as the argument value. is functionally equivalent to the default . +For asynchronous methods that want to expose the ability to be canceled first and foremost, you don't have to provide an overload that doesn't accept a cancellation token. For methods that can't be canceled, don't provide overloads that accept a cancellation token; this helps indicate to the caller whether the target method is actually cancelable. Consumer code that doesn't desire cancellation can call a method that accepts a and provide as the argument value. is functionally equivalent to the default . ## Progress reporting (optional) - Some asynchronous operations benefit from providing progress notifications; these are typically used to update a user interface with information about the progress of the asynchronous operation. +Some asynchronous operations benefit from providing progress notifications. Typically, use these notifications to update a user interface with information about the progress of the asynchronous operation. - In TAP, progress is handled through an interface, which is passed to the asynchronous method as a parameter that is usually named `progress`. Providing the progress interface when the asynchronous method is called helps eliminate race conditions that result from incorrect usage (that is, when event handlers that are incorrectly registered after the operation starts may miss updates). More importantly, the progress interface supports varying implementations of progress, as determined by the consuming code. For example, the consuming code may only care about the latest progress update, or may want to buffer all updates, or may want to invoke an action for each update, or may want to control whether the invocation is marshalled to a particular thread. All these options may be achieved by using a different implementation of the interface, customized to the particular consumer's needs. As with cancellation, TAP implementations should provide an parameter only if the API supports progress notifications. +In TAP, handle progress through an interface. Pass this interface to the asynchronous method as a parameter, usually named `progress`. When you provide the progress interface at the time of calling the asynchronous method, you help eliminate race conditions that result from incorrect usage. These race conditions occur when event handlers are incorrectly registered after the operation starts and miss updates. More importantly, the progress interface supports varying implementations of progress, as determined by the consuming code. For example, the consuming code might only care about the latest progress update, or it might want to buffer all updates, invoke an action for each update, or control whether the invocation is marshalled to a particular thread. All these options are achievable by using different implementations of the interface, customized to the particular consumer's needs. As with cancellation, TAP implementations should provide an parameter only if the API supports progress notifications. - For example, if the `ReadAsync` method discussed earlier in this article is able to report intermediate progress in the form of the number of bytes read thus far, the progress callback could be an interface: +For example, if the `ReadAsync` method discussed earlier in this article can report intermediate progress in the form of the number of bytes read thus far, the progress callback could be an interface: - [!code-csharp[Conceptual.TAP#2](../../../samples/snippets/csharp/VS_Snippets_CLR/conceptual.tap/cs/examples1.cs#2)] - [!code-vb[Conceptual.TAP#2](../../../samples/snippets/visualbasic/VS_Snippets_CLR/conceptual.tap/vb/examples1.vb#2)] +:::code language="csharp" source="./snippets/task-based-asynchronous-pattern-tap/csharp/Program.cs" id="ProgressParameter"::: +:::code language="vb" source="./snippets/task-based-asynchronous-pattern-tap/vb/Program.vb" id="ProgressParameter"::: - If a `FindFilesAsync` method returns a list of all files that meet a particular search pattern, the progress callback could provide an estimate of the percentage of work completed and the current set of partial results. It could provide this information with either a tuple: +If a `FindFilesAsync` method returns a list of all files that meet a particular search pattern, the progress callback could provide an estimate of the percentage of work completed and the current set of partial results. It could provide this information with either a tuple: - [!code-csharp[Conceptual.TAP#3](../../../samples/snippets/csharp/VS_Snippets_CLR/conceptual.tap/cs/examples1.cs#3)] - [!code-vb[Conceptual.TAP#3](../../../samples/snippets/visualbasic/VS_Snippets_CLR/conceptual.tap/vb/examples1.vb#3)] +:::code language="csharp" source="./snippets/task-based-asynchronous-pattern-tap/csharp/Program.cs" id="ProgressTupleParameter"::: +:::code language="vb" source="./snippets/task-based-asynchronous-pattern-tap/vb/Program.vb" id="ProgressTupleParameter"::: - or with a data type that's specific to the API: +or with a data type that's specific to the API: - [!code-csharp[Conceptual.TAP#4](../../../samples/snippets/csharp/VS_Snippets_CLR/conceptual.tap/cs/examples1.cs#4)] - [!code-vb[Conceptual.TAP#4](../../../samples/snippets/visualbasic/VS_Snippets_CLR/conceptual.tap/vb/examples1.vb#4)] +:::code language="csharp" source="./snippets/task-based-asynchronous-pattern-tap/csharp/Program.cs" id="ProgressInfoParameter"::: +:::code language="vb" source="./snippets/task-based-asynchronous-pattern-tap/vb/Program.vb" id="ProgressInfoParameter"::: - In the latter case, the special data type is usually suffixed with `ProgressInfo`. +In the latter case, the special data type is usually suffixed with `ProgressInfo`. - If TAP implementations provide overloads that accept a `progress` parameter, they must allow the argument to be `null`, in which case no progress is reported. TAP implementations should report the progress to the object synchronously, which enables the asynchronous method to quickly provide progress. It also allows the consumer of the progress to determine how and where best to handle the information. For example, the progress instance could choose to marshal callbacks and raise events on a captured synchronization context. +If TAP implementations provide overloads that accept a `progress` parameter, they must allow the argument to be `null`. If you pass `null`, no progress is reported. TAP implementations should report the progress to the object synchronously, which enables the asynchronous method to quickly provide progress. It also allows the consumer of the progress to determine how and where best to handle the information. For example, the progress instance could choose to marshal callbacks and raise events on a captured synchronization context. ## IProgress\ implementations @@ -106,11 +123,11 @@ public class Progress : IProgress } ``` - An instance of exposes a event, which is raised every time the asynchronous operation reports a progress update. The event is raised on the object that was captured when the instance was instantiated. If no synchronization context was available, a default context that targets the thread pool is used. Handlers may be registered with this event. A single handler may also be provided to the constructor for convenience, and behaves just like an event handler for the event. Progress updates are raised asynchronously to avoid delaying the asynchronous operation while event handlers are executing. Another implementation could choose to apply different semantics. +An instance of exposes a event, which is raised every time the asynchronous operation reports a progress update. The event is raised on the object that the instance captures when it's instantiated. If no synchronization context is available, a default context that targets the thread pool is used. You can register handlers with this event. For convenience, you can also provide a single handler to the constructor. This handler behaves just like an event handler for the event. Progress updates are raised asynchronously to avoid delaying the asynchronous operation while event handlers are executing. Another implementation could choose to apply different semantics. ## Choosing the overloads to provide - If a TAP implementation uses both the optional and optional parameters, it could potentially require up to four overloads: +If a TAP implementation uses both the optional and optional parameters, it could potentially require up to four overloads: ```csharp public Task MethodNameAsync(…); @@ -128,7 +145,7 @@ Public MethodNameAsync(…, cancellationToken As CancellationToken, progress As IProgress(Of T)) As Task ``` - However, many TAP implementations don't provide cancellation or progress capabilities, so they require a single method: +However, many TAP implementations don't provide cancellation or progress capabilities, so they require a single method: ```csharp public Task MethodNameAsync(…); @@ -138,7 +155,7 @@ public Task MethodNameAsync(…); Public MethodNameAsync(…) As Task ``` - If a TAP implementation supports either cancellation or progress but not both, it may provide two overloads: +If a TAP implementation supports either cancellation or progress but not both, it might provide two overloads: ```csharp public Task MethodNameAsync(…); @@ -160,7 +177,7 @@ Public MethodNameAsync(…) As Task Public MethodNameAsync(…, progress As IProgress(Of T)) As Task ``` - If a TAP implementation supports both cancellation and progress, it may expose all four overloads. However, it may provide only the following two: +If a TAP implementation supports both cancellation and progress, it might expose all four overloads. However, it might provide only the following two: ```csharp public Task MethodNameAsync(…); @@ -174,17 +191,15 @@ Public MethodNameAsync(…, cancellationToken As CancellationToken, progress As IProgress(Of T)) As Task ``` - To compensate for the two missing intermediate combinations, developers may pass or a default for the `cancellationToken` parameter and `null` for the `progress` parameter. +To compensate for the two missing intermediate combinations, developers can pass or a default for the `cancellationToken` parameter and `null` for the `progress` parameter. - If you expect every usage of the TAP method to support cancellation or progress, you may omit the overloads that don't accept the relevant parameter. +If you expect every usage of the TAP method to support cancellation or progress, you can omit the overloads that don't accept the relevant parameter. - If you decide to expose multiple overloads to make cancellation or progress optional, the overloads that don't support cancellation or progress should behave as if they passed for cancellation or `null` for progress to the overload that does support these. +If you decide to expose multiple overloads to make cancellation or progress optional, the overloads that don't support cancellation or progress should behave as if they passed for cancellation or `null` for progress to the overload that does support these parameters. ## Related articles -|Title|Description| -|-----------|-----------------| -|[Asynchronous Programming Patterns](index.md)|Introduces the three patterns for performing asynchronous operations: the Task-based Asynchronous Pattern (TAP), the Asynchronous Programming Model (APM), and the Event-based Asynchronous Pattern (EAP).| -|[Implementing the Task-based Asynchronous Pattern](implementing-the-task-based-asynchronous-pattern.md)|Describes how to implement the Task-based Asynchronous Pattern (TAP) in three ways: by using the C# and Visual Basic compilers in Visual Studio, manually, or through a combination of the compiler and manual methods.| -|[Consuming the Task-based Asynchronous Pattern](consuming-the-task-based-asynchronous-pattern.md)|Describes how you can use tasks and callbacks to achieve waiting without blocking.| -|[Interop with Other Asynchronous Patterns and Types](interop-with-other-asynchronous-patterns-and-types.md)|Describes how to use the Task-based Asynchronous Pattern (TAP) to implement the Asynchronous Programming Model (APM) and Event-based Asynchronous Pattern (EAP).| +- [Asynchronous Programming Patterns](index.md) — Introduces the three patterns for performing asynchronous operations: the Task-based Asynchronous Pattern (TAP), the Asynchronous Programming Model (APM), and the Event-based Asynchronous Pattern (EAP). +- [Implementing the Task-based Asynchronous Pattern](implementing-the-task-based-asynchronous-pattern.md) — Describes how to implement TAP in three ways: by using the C# and Visual Basic compilers in Visual Studio, manually, or through a combination of the compiler and manual methods. +- [Consuming the Task-based Asynchronous Pattern](consuming-the-task-based-asynchronous-pattern.md) — Describes how you can use tasks and callbacks to achieve waiting without blocking. +- [Interop with Other Asynchronous Patterns and Types](interop-with-other-asynchronous-patterns-and-types.md) — Describes how to use TAP to implement the Asynchronous Programming Model (APM) and Event-based Asynchronous Pattern (EAP). diff --git a/samples/snippets/csharp/VS_Snippets_CLR/conceptual.tap/cs/examples1.cs b/samples/snippets/csharp/VS_Snippets_CLR/conceptual.tap/cs/examples1.cs deleted file mode 100644 index c600b13ea22ff..0000000000000 --- a/samples/snippets/csharp/VS_Snippets_CLR/conceptual.tap/cs/examples1.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -public class Example -{ - public static void Main() - { - } - - // - public Task ReadAsync(byte [] buffer, int offset, int count, - CancellationToken cancellationToken) - // - { - return Task.Factory.StartNew( () => Thread.Sleep(100)); - } - - // - public Task ReadAsync(byte[] buffer, int offset, int count, - IProgress progress) - // - { - return Task.Factory.StartNew( () => Thread.Sleep(100)); - } - - // - public Task> FindFilesAsync( - string pattern, - IProgress>>> progress) - // - { - return Task.Factory.StartNew( () => { FileInfo[] fi = new FileInfo[10]; - return new ReadOnlyCollection(fi); } ); - } - - // - public Task> FindFilesAsync( - string pattern, - IProgress progress) - // - { - return Task.Factory.StartNew( () => { FileInfo[] fi = new FileInfo[10]; - return new ReadOnlyCollection(fi); } ); - } -} - -public class FindFilesProgressInfo -{} - -public class Progress : IProgress -{ - public Progress(); - public Progress(Action handler); - protected virtual void OnReport(T value); - public event EventHandler ProgressChanged; -} diff --git a/samples/snippets/csharp/VS_Snippets_CLR/conceptual.tap_patterns/cs/patterns1.cs b/samples/snippets/csharp/VS_Snippets_CLR/conceptual.tap_patterns/cs/patterns1.cs deleted file mode 100644 index 52ca549350ebf..0000000000000 --- a/samples/snippets/csharp/VS_Snippets_CLR/conceptual.tap_patterns/cs/patterns1.cs +++ /dev/null @@ -1,145 +0,0 @@ -using System; -using System.IO; -using System.Runtime.CompilerServices; -using System.Drawing; -using System.Threading; -using System.Threading.Tasks; - -public static class StaticPattern -{ - // - public static Task ReadTask(this Stream stream, byte[] buffer, int offset, int count, object state) - { - var tcs = new TaskCompletionSource(); - stream.BeginRead(buffer, offset, count, ar => - { - try { tcs.SetResult(stream.EndRead(ar)); } - catch (Exception exc) { tcs.SetException(exc); } - }, state); - return tcs.Task; - } - // - - // - public static Task Delay(int millisecondsTimeout) - { - TaskCompletionSource tcs = null; - Timer timer = null; - - timer = new Timer(delegate - { - timer.Dispose(); - tcs.TrySetResult(DateTimeOffset.UtcNow); - }, null, Timeout.Infinite, Timeout.Infinite); - - tcs = new TaskCompletionSource(timer); - timer.Change(millisecondsTimeout, Timeout.Infinite); - return tcs.Task; - } - // - - // - public static async Task Poll(Uri url, CancellationToken cancellationToken, - IProgress progress) - { - while(true) - { - await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken); - bool success = false; - try - { - await DownloadStringAsync(url); - success = true; - } - catch { /* ignore errors */ } - progress.Report(success); - } - } - // - - static Task DownloadStringAsync(Uri url) - { - var tcs = new TaskCompletionSource(); - return tcs.Task; - } -} - -public class Pattern -{ - int value = 0; - - // - public Task MethodAsync(string input) - { - if (input == null) throw new ArgumentNullException("input"); - return MethodAsyncInternal(input); - } - - private async Task MethodAsyncInternal(string input) - { - - // code that uses await goes here - - return value; - } - // - - // - internal Task RenderAsync( - ImageData data, CancellationToken cancellationToken) - { - return Task.Run(() => - { - var bmp = new Bitmap(data.Width, data.Height); - for(int y=0; y - - // - public static Task Delay(int millisecondsTimeout) - { - TaskCompletionSource tcs = null; - Timer timer = null; - - timer = new Timer(delegate - { - timer.Dispose(); - tcs.TrySetResult(true); - }, null, Timeout.Infinite, Timeout.Infinite); - - tcs = new TaskCompletionSource(timer); - timer.Change(millisecondsTimeout, Timeout.Infinite); - return tcs.Task; - } - // - - // - public async Task DownloadDataAndRenderImageAsync( - CancellationToken cancellationToken) - { - var imageData = await DownloadImageDataAsync(cancellationToken); - return await RenderAsync(imageData, cancellationToken); - } - // - - private async Task DownloadImageDataAsync(CancellationToken c) - { - // return new TaskCompletionSource().Task; - return new ImageData(); - } -} - -internal class ImageData -{ - public int Width = 0; - public int Height = 0; -} \ No newline at end of file diff --git a/samples/snippets/visualbasic/VS_Snippets_CLR/conceptual.tap/vb/examples1.vb b/samples/snippets/visualbasic/VS_Snippets_CLR/conceptual.tap/vb/examples1.vb deleted file mode 100644 index 25789180473fb..0000000000000 --- a/samples/snippets/visualbasic/VS_Snippets_CLR/conceptual.tap/vb/examples1.vb +++ /dev/null @@ -1,57 +0,0 @@ -' Visual Basic .NET Document -Option Strict On - -Imports System.Collections.Generic -Imports System.Collections.ObjectModel -Imports System.IO -Imports System.Threading -Imports System.Threading.Tasks - -Module Example - Public Sub Main() - - End Sub - - ' - Public Function ReadAsync(buffer() As Byte, offset As Integer, - count As Integer, - cancellationToken As CancellationToken) _ - As Task - ' - Return Task.Factory.StartNew(Sub() Thread.Sleep(100)) - End Function - - ' - Public Function ReadAsync(buffer() As Byte, offset As Integer, - count As Integer, - progress As IProgress(Of Long)) As Task - ' - Return Task.Factory.StartNew(Sub() Thread.Sleep(100)) - End Function - - - ' - Public Function FindFilesAsync(pattern As String, - progress As IProgress(Of Tuple(Of Double, ReadOnlyCollection(Of List(Of FileInfo))))) _ - As Task(Of ReadOnlyCollection(Of FileInfo)) - ' - Return Task.Factory.StartNew(Function() - Dim fi(9) As FileInfo - Return New ReadOnlyCollection(Of FileInfo)(fi) - End Function) - End Function - - ' - Public Function FindFilesAsync(pattern As String, - progress As IProgress(Of FindFilesProgressInfo)) _ - As Task(Of ReadOnlyCollection(Of FileInfo)) - ' - Return Task.Factory.StartNew(Function() - Dim fi(9) As FileInfo - Return New ReadOnlyCollection(Of FileInfo)(fi) - End Function) - End Function -End Module - -Public Class FindFilesProgressInfo -End Class