Creare attività precalcolate
In questo articolo viene descritto come utilizzare il metodo Task.FromResult per recuperare i risultati di operazioni di download asincrone contenute in una cache. Tramite il metodo FromResult viene restituito un oggetto Task<TResult> finito contenente il valore fornito come relativa proprietà Result. Questo metodo è utile quando si esegue un'operazione asincrona che restituisce un oggetto Task<TResult> e il risultato di tale oggetto Task<TResult> è già calcolato.
Esempio
Nell'esempio seguente vengono scaricate stringhe dal Web. Viene definito il metodo DownloadStringAsync
. Tramite questo metodo vengono scaricate stringhe dal Web in modo asincrono. In questo esempio viene inoltre utilizzato un oggetto ConcurrentDictionary<TKey,TValue> per memorizzare nella cache i risultati di operazioni precedenti. Se l'indirizzo di input viene mantenuto in questa cache, in DownloadStringAsync
viene utilizzato il metodo FromResult per produrre un oggetto Task<TResult> con il contenuto in corrispondenza dell'indirizzo in questione. In caso contrario, tramite DownloadStringAsync
viene scaricato il file dal Web e viene aggiunto il risultato alla cache.
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://zcusa.951200.xyz/aspnet/core",
"https://zcusa.951200.xyz/dotnet",
"https://zcusa.951200.xyz/dotnet/architecture/dapr-for-net-developers",
"https://zcusa.951200.xyz/dotnet/azure",
"https://zcusa.951200.xyz/dotnet/desktop/wpf",
"https://zcusa.951200.xyz/dotnet/devops/create-dotnet-github-action",
"https://zcusa.951200.xyz/dotnet/machine-learning",
"https://zcusa.951200.xyz/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://zcusa.951200.xyz/aspnet/core",
"https://zcusa.951200.xyz/dotnet",
"https://zcusa.951200.xyz/dotnet/architecture/dapr-for-net-developers",
"https://zcusa.951200.xyz/dotnet/azure",
"https://zcusa.951200.xyz/dotnet/desktop/wpf",
"https://zcusa.951200.xyz/dotnet/devops/create-dotnet-github-action",
"https://zcusa.951200.xyz/dotnet/machine-learning",
"https://zcusa.951200.xyz/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
Nell'esempio precedente, la prima volta che viene scaricato ogni URL, il relativo valore viene archiviato nella cache. Il metodo FromResult consente al metodo DownloadStringAsync
di creare oggetti di Task<TResult> contenenti questi risultati precalcolati. Le chiamate successive per scaricare la stringa restituiscono i valori memorizzati nella cache e l’operazione è molto più veloce.