Reputation: 1614
I don't have much experience in C# async.
Task - load bitmaps from the internet. Before, I was just loading them 1 by 1, in sync. Loading them in async would give results quicker. Down below, I made two examples, how I could get single image - GetImage
and GetImageAsync
. And for a list of Images, I would use LoadImages
and LoadImages2
.
LoadImages
would run sync functions in async (all at the same time (?)), LoadImages2
would run async functions in async and produce the same result (?).
The thing, that I don't fully understand - in GetImageAsync
await request.GetResponseAsync()
. Do I really need it? Is it a "better" way of doing the same thing? Are there really any difference between LoadImages
and LoadImages2
.
At the moment, I am thinking of choosing GetImage
and LoadImages
option. Also, I don't want to decorate every function with async Task
, I only need to load these images in async.
public Bitmap GetImage(string url)
{
HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
using (WebResponse response = request.GetResponse())
using (Stream responseStream = response.GetResponseStream())
return new Bitmap(responseStream);
}
public async Task<Bitmap> GetImageAsync(string url)
{
HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
using (WebResponse response = await request.GetResponseAsync())
using (Stream responseStream = response.GetResponseStream())
return new Bitmap(responseStream);
}
private Dictionary<string, Bitmap> LoadImages(List<string> urls)
{
Dictionary<string, Bitmap> images = new Dictionary<string, Bitmap>();
Task.WaitAll(urls.Select(url =>
Task.Run(() =>
{
images.Add(url, GetImage(url));
})).ToArray());
return images;
}
private Dictionary<string, Bitmap> LoadImages2(List<string> urls)
{
Dictionary<string, Bitmap> images = new Dictionary<string, Bitmap>();
Task.WhenAll(urls.Select(url =>
Task.Run(async () =>
{
images.Add(url, await GetImageAsync(url));
})));
return images;
}
Upvotes: 1
Views: 1504
Reputation: 13458
Since you insist on wrapping your call synchronously, you could try something along the lines of
private Dictionary<string, Bitmap> LoadImages(List<string> urls)
{
var result = new Dictionary<string, Bitmap>();
// start tasks, associate each task with its url
var tasks = urls.Select(x => new { url = x, imgTask = GetImageAsync(x) });
// wait for all tasks to complete
Task.WhenAll(tasks.Select(x => x.imgTask)).Wait();
// transform the task results into your desired result format
foreach (var item in tasks)
{
result.Add(item.url, item.imgTask.Result);
}
return result;
}
However, I'm not 100% confident that the Task.WhenAll(...).Wait()
construct is completely deadlock-free in all situations. Avoiding deadlocks is the tricky part in switching between sync and async code. It would be better to make LoadImages
async, as Stephan Cleary suggested. Its a common observation, that async code tends to "infect" your synchronous code and you have to code async all the way in the end.
Upvotes: 0
Reputation: 457177
There's some confusion around terminology and technology choices here.
Before, I was just loading them 1 by 1, in sync. Loading them in async would give results quicker.
What you mean is serial versus concurrent, not sync versus async. Serial is one-at-a-time, and concurrent is multiple things at once. Synchronous code can be serial or concurrent, and asynchronous code can be serial or concurrent.
Secondly, concurrency versus parallelism. Task.Run
is a form of parallelism, which is a way to achieve concurrency by adding threads to the problem. Asynchrony is a way to achieve concurrency by freeing up threads.
LoadImages
is an example of using parallelism with synchronous code. The advantage of this approach is it keeps the top-level method synchronous, so none of the calling code has to change. The disadvantage is that it's wasteful in terms of resource use, and not a good conceptual fit for what's going on underneath (I/O-bound code is more naturally represented by asynchronous APIs).
LoadImages2
is a mixture of parallel and asynchronous code which is a bit confusing. Asynchronous concurrency is more easily represented without threads (i.e., Task.Run
). It's also more natural to return values rather than update collections as side effects. So, something like this:
private async Task<Dictionary<string, Bitmap>> LoadImagesAsync(List<string> urls)
{
Bitmap[] result = await Task.WhenAll(urls.Select(url => GetImageAsync(url)));
return Enumerable.Range(0, urls.Length).ToDictionary(i => urls[i], i => result[i]);
}
P.S. If you do decide to go with (the synchronous) LoadImages
, you'll need to fix a race condition where the various parallel threads will all try to update the dictionary without locking it.
Upvotes: 4