When reading
How to: Create Pre-Computed Tasks
the example method DownloadStringAsync
returns
Task.Run( async ()=> { return await new WebClient().DownloadStringTaskAsync(address)})
I wonder why we need to wrap a async method in a Task.Run()
? the WebClient().DownloadStringTaskAsync()
method returns a Task
itself.
I think the problem is that they wanted to show how to use Task.FromResult
and then tied themselves into knots because they wanted to consume a Task
returning method.
The natural way these days to write code that consumes Task
returning methods is to make them async
. But then, if you do that, Task.FromResult
disappears:
// Asynchronously downloads the requested resource as a string.
public static async Task<string> DownloadStringAsync(string address)
{
// First try to retrieve the content from cache.
string content;
if (cachedDownloads.TryGetValue(address, out content))
{
return content;
}
content = await new WebClient().DownloadStringTaskAsync(address);
cachedDownloads.TryAdd(address, content);
return content;
}
Simpler code, still achieves the overall goal. Unless you expect cachedDownloads.TryAdd
to be significantly CPU-heavy in which case their version also guarantees to push that into running in the thread pool.
In short - don't copy this code, it's not a good example to work from2.
This is the version that avoids allocating the async
state machine1 when not required, shows Task.FromResult
and still doesn't use Task.Run
:
// Asynchronously downloads the requested resource as a string.
public static Task<string> DownloadStringAsync(string address)
{
// First try to retrieve the content from cache.
string content;
if (cachedDownloads.TryGetValue(address, out content))
{
return Task.FromResult(content);
}
return DownloadStringSlowAsync(address);
}
private static async Task<string> DownloadStringSlowAsync(string address)
{
string content = await new WebClient().DownloadStringTaskAsync(address);
cachedDownloads.TryAdd(address, content);
return content;
}
Even betterer: (no, it's not a word, I don't care)
static ConcurrentDictionary<string, Task<string>> cachedDownloads =
new ConcurrentDictionary<string, Task<string>>();
// Asynchronously downloads the requested resource as a string.
public static Task<string> DownloadStringAsync(string address)
{
// First try to retrieve the content from cache.
Task<string> content;
if (cachedDownloads.TryGetValue(address, out content))
{
return content;
}
return DownloadStringSlowAsync(address);
}
private static async Task<string> DownloadStringSlowAsync(string address)
{
string content = await new WebClient().DownloadStringTaskAsync(address);
cachedDownloads.TryAdd(address, Task.FromResult(content));
return content;
}
Because now our cache only contains completed tasks and we can just hand them out over and over rather than repeatedly allocating new Task
objects on every request.
Any of these approaches is, of course, only really viable if the cached objects (string
here) are immutable.
1Don't do this automatically. It should be a deliberate, considered decision based upon whether such allocations are causing performance issues.
2It's also a bad example of a cache, since as Raymond Chen points out, A cache with a bad policy is another name for a memory leak. In this example there's no expiration at all.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With