C#在同步上下文中运行异步任务

我没有太多C#异步经验。

任务 – 从互联网加载位图。 之前,我只是逐个加载它们,同步。 将它们加载到异步中可以更快地获得结果。 在下面,我做了两个例子,我如何获得单个图像 – GetImageGetImageAsync 。 对于图像列表,我将使用LoadImagesLoadImages2

LoadImages将在异步中运行同步函数(所有这些都在同一时间(?)), LoadImages2将在异步中运行异步函数并产生相同的结果(?)。 我不完全理解的事情 – 在GetImageAsync await request.GetResponseAsync() 。 我真的需要它吗? 做同样的事情是一种“更好”的方式吗? LoadImagesLoadImages2之间是否存在任何差异?

目前,我正在考虑选择LoadImagesLoadImages选项。 另外,我不想用async Task装饰每个函数,我只需要在异步中加载这些图像。

 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 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 LoadImages(List urls) { Dictionary images = new Dictionary(); Task.WaitAll(urls.Select(url => Task.Run(() => { images.Add(url, GetImage(url)); })).ToArray()); return images; } private Dictionary LoadImages2(List urls) { Dictionary images = new Dictionary(); Task.WhenAll(urls.Select(url => Task.Run(async () => { images.Add(url, await GetImageAsync(url)); }))); return images; } 

这里的术语和技术选择存在一些混淆。

之前,我只是逐个加载它们,同步。 将它们加载到异步中可以更快地获得结果。

你的意思是串行并发 ,而不是同步异步 。 串行是一次一个,并发同时是多个事情。 同步代码可以是串行或并发的,异步代码可以是串行或并发的。

其次, 并发性并行性Task.Run是一种并行forms,它是通过向问题添加线程来实现并发的一种方法。 异步是一种通过释放线程来实现并发的方法。

LoadImages是使用具有同步代码的并行性的示例。 这种方法的优点是它使顶级方法保持同步,因此所有调用代码都不得更改。 缺点是它在资源使用方面是浪费的,并且不适合于下面发生的事情(I / O绑定代码更自然地由异步API表示)。

LoadImages2是并行和异步代码的混合,有点令人困惑。 没有线程(即Task.Run )更容易表示异步并发。 返回值更自然,而不是将集合更新为副作用。 所以,像这样:

 private async Task> LoadImagesAsync(List urls) { Bitmap[] result = await Task.WhenAll(urls.Select(url => GetImageAsync(url))); return Enumerable.Range(0, urls.Length).ToDictionary(i => urls[i], i => result[i]); } 

PS如果您决定使用(同步) LoadImages ,则需要修复竞争条件,其中各种并行线程都将尝试更新字典而不锁定它。

既然你坚持同步包装你的电话,你可以尝试一些方法

 private Dictionary LoadImages(List urls) { var result = new Dictionary(); // 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; } 

但是,我并非100%确信Task.WhenAll(...).Wait()构造在所有情况下都完全没有死锁。 避免死锁是同步和异步代码之间切换的棘手问题。 如Stephan Cleary建议的那样,最好使LoadImages异步。 这是一个常见的观察结果,即异步代码往往会“感染”您的同步代码,并且您必须最终编写异步代码。