我没有太多C#异步经验。
任务 - 从互联网加载位图。之前,我只是逐个加载它们,同步。将它们加载到异步中可以更快地获得结果。在下面,我做了两个例子,我如何获得单张图片 - GetImage
和GetImageAsync
。对于图片列表,我会使用LoadImages
和LoadImages2
。
LoadImages
将在异步中运行同步函数(所有同时(?)),LoadImages2
将在异步中运行异步函数并生成相同的结果(?)。
我不完全理解的事情 - GetImageAsync
await request.GetResponseAsync()
。我真的需要它吗?做同样的事情是一种“更好”的方式吗? LoadImages
和LoadImages2
之间是否存在任何差异?
目前,我正在考虑选择GetImage
和LoadImages
选项。另外,我不想用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<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;
}
答案 0 :(得分:4)
这里的术语和技术选择存在一些混淆。
之前,我只是逐个加载它们,同步。将它们加载到异步中可以更快地获得结果。
您的意思是串行与并发,而不是同步与 async 。串行是一次一个,并发同时是多个事情。同步代码可以是串行或并发的,异步代码可以是串行或并发的。
其次,并发与并行。 Task.Run
是一种并行形式,它是通过向问题添加线程来实现并发的一种方法。 Asynchrony 是一种通过释放线程来实现并发的方法。
LoadImages
是使用并行与同步代码的示例。这种方法的优点是它使顶级方法保持同步,因此所有调用代码都不得更改。缺点是它在资源使用方面浪费,而且不适合下面发生的事情(I / O绑定代码更自然地由异步API表示)。
LoadImages2
是并行和异步代码的混合,有点令人困惑。没有线程(即Task.Run
)更容易表示异步并发。返回值也更自然,而不是将集合更新为副作用。所以,像这样:
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。如果您决定使用(同步)LoadImages
,则需要修复竞争条件,其中各种并行线程都将尝试更新字典而不锁定它。
答案 1 :(得分:0)
由于你坚持同步包装你的电话,你可以尝试一下
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;
}
但是,我并非100%确信Task.WhenAll(...).Wait()
构造在所有情况下都完全没有死锁。避免死锁是同步和异步代码之间切换的棘手问题。如Stephan Cleary建议的那样,最好使LoadImages
异步。这是一个常见的观察,即异步代码往往会“感染”您的同步代码,并且您必须最终编码异步。