Creación de tareas precalculadas
En este artículo va a aprender a usar el método Task.FromResult para recuperar los resultados de operaciones asincrónicas de descarga que se conservan en una caché. El método FromResult devuelve un objeto Task<TResult> terminado que contiene el valor proporcionado como su propiedad Result. Este método es útil cuando se realiza una operación asincrónica que devuelve un objeto Task<TResult> y el resultado de ese objeto Task<TResult> ya se ha calculado.
Ejemplo
En el ejemplo siguiente se descargan cadenas desde la Web. Define el método DownloadStringAsync
. Este método descarga cadenas de la Web de forma asincrónica. En este ejemplo también se usa un objeto ConcurrentDictionary<TKey,TValue> para almacenar en caché los resultados de las operaciones anteriores. Si la dirección de entrada se mantiene en esta memoria caché, DownloadStringAsync
utiliza el método FromResult para generar un objeto Task<TResult> que incluye el contenido en esa dirección. En caso contrario, DownloadStringAsync
descarga el archivo desde la Web y agrega el resultado a la memoria caché.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
public static class DownloadCache
{
private static readonly ConcurrentDictionary<string, string> s_cachedDownloads = new();
private static readonly HttpClient s_httpClient = new();
public static Task<string> DownloadStringAsync(string address)
{
if (s_cachedDownloads.TryGetValue(address, out string? content))
{
return Task.FromResult(content);
}
return Task.Run(async () =>
{
content = await s_httpClient.GetStringAsync(address);
s_cachedDownloads.TryAdd(address, content);
return content;
});
}
public static async Task Main()
{
string[] urls = new[]
{
"https://learn.microsoft.com/aspnet/core",
"https://learn.microsoft.com/dotnet",
"https://learn.microsoft.com/dotnet/architecture/dapr-for-net-developers",
"https://learn.microsoft.com/dotnet/azure",
"https://learn.microsoft.com/dotnet/desktop/wpf",
"https://learn.microsoft.com/dotnet/devops/create-dotnet-github-action",
"https://learn.microsoft.com/dotnet/machine-learning",
"https://learn.microsoft.com/xamarin",
"https://dotnet.microsoft.com/",
"https://www.microsoft.com"
};
Stopwatch stopwatch = Stopwatch.StartNew();
IEnumerable<Task<string>> downloads = urls.Select(DownloadStringAsync);
static void StopAndLogElapsedTime(
int attemptNumber, Stopwatch stopwatch, Task<string[]> downloadTasks)
{
stopwatch.Stop();
int charCount = downloadTasks.Result.Sum(result => result.Length);
long elapsedMs = stopwatch.ElapsedMilliseconds;
Console.WriteLine(
$"Attempt number: {attemptNumber}\n" +
$"Retrieved characters: {charCount:#,0}\n" +
$"Elapsed retrieval time: {elapsedMs:#,0} milliseconds.\n");
}
await Task.WhenAll(downloads).ContinueWith(
downloadTasks => StopAndLogElapsedTime(1, stopwatch, downloadTasks));
// Perform the same operation a second time. The time required
// should be shorter because the results are held in the cache.
stopwatch.Restart();
downloads = urls.Select(DownloadStringAsync);
await Task.WhenAll(downloads).ContinueWith(
downloadTasks => StopAndLogElapsedTime(2, stopwatch, downloadTasks));
}
// Sample output:
// Attempt number: 1
// Retrieved characters: 754,585
// Elapsed retrieval time: 2,857 milliseconds.
// Attempt number: 2
// Retrieved characters: 754,585
// Elapsed retrieval time: 1 milliseconds.
}
Imports System.Collections.Concurrent
Imports System.Net.Http
Module Snippets
Class DownloadCache
Private Shared ReadOnly s_cachedDownloads As ConcurrentDictionary(Of String, String) =
New ConcurrentDictionary(Of String, String)()
Private Shared ReadOnly s_httpClient As HttpClient = New HttpClient()
Public Function DownloadStringAsync(address As String) As Task(Of String)
Dim content As String = Nothing
If s_cachedDownloads.TryGetValue(address, content) Then
Return Task.FromResult(Of String)(content)
End If
Return Task.Run(
Async Function()
content = Await s_httpClient.GetStringAsync(address)
s_cachedDownloads.TryAdd(address, content)
Return content
End Function)
End Function
End Class
Public Sub StopAndLogElapsedTime(
attemptNumber As Integer,
stopwatch As Stopwatch,
downloadTasks As Task(Of String()))
stopwatch.Stop()
Dim charCount As Integer = downloadTasks.Result.Sum(Function(result) result.Length)
Dim elapsedMs As Long = stopwatch.ElapsedMilliseconds
Console.WriteLine(
$"Attempt number: {attemptNumber}{vbCrLf}" &
$"Retrieved characters: {charCount:#,0}{vbCrLf}" &
$"Elapsed retrieval time: {elapsedMs:#,0} milliseconds.{vbCrLf}")
End Sub
Sub Main()
Dim cache As DownloadCache = New DownloadCache()
Dim urls As String() = {
"https://learn.microsoft.com/aspnet/core",
"https://learn.microsoft.com/dotnet",
"https://learn.microsoft.com/dotnet/architecture/dapr-for-net-developers",
"https://learn.microsoft.com/dotnet/azure",
"https://learn.microsoft.com/dotnet/desktop/wpf",
"https://learn.microsoft.com/dotnet/devops/create-dotnet-github-action",
"https://learn.microsoft.com/dotnet/machine-learning",
"https://learn.microsoft.com/xamarin",
"https://dotnet.microsoft.com/",
"https://www.microsoft.com"
}
Dim stopwatch As Stopwatch = Stopwatch.StartNew()
Dim downloads As IEnumerable(Of Task(Of String)) =
urls.Select(AddressOf cache.DownloadStringAsync)
Task.WhenAll(downloads).ContinueWith(
Sub(downloadTasks)
StopAndLogElapsedTime(1, stopwatch, downloadTasks)
End Sub).Wait()
stopwatch.Restart()
downloads = urls.Select(AddressOf cache.DownloadStringAsync)
Task.WhenAll(downloads).ContinueWith(
Sub(downloadTasks)
StopAndLogElapsedTime(2, stopwatch, downloadTasks)
End Sub).Wait()
End Sub
' Sample output:
' Attempt number 1
' Retrieved characters: 754,585
' Elapsed retrieval time: 2,857 milliseconds.
'
' Attempt number 2
' Retrieved characters: 754,585
' Elapsed retrieval time: 1 milliseconds.
End Module
En el ejemplo anterior, la primera vez que se descarga cada dirección URL, su valor se almacena en la caché. El método FromResult habilita el método DownloadStringAsync
para crear objetos Task<TResult> que contienen estos resultados precalculados. Las llamadas posteriores para descargar la cadena devuelven los valores almacenados en caché, y son mucho más rápidas.