C# running async tasks in sync context

主宰稳场 提交于 2019-12-23 02:39:22

问题


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;
}

回答1:


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.




回答2:


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.



来源:https://stackoverflow.com/questions/35914053/c-sharp-running-async-tasks-in-sync-context

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!