[英]C# running async tasks in sync context
我在C#异步方面没有太多经验。
任务-从Internet加载位图。 以前,我只是按1个1个同步地加载它们。 异步加载它们可以更快地得到结果。 在下面,我举了两个示例,如何获取单个图像GetImage
和GetImageAsync
。 对于图像列表,我将使用LoadImages
和LoadImages2
。
LoadImages
将在异步中运行同步功能(全部同时(?)), LoadImages2
将在异步中运行异步功能并产生相同的结果(?)。 我不完全了解的事情-在GetImageAsync
await request.GetResponseAsync()
。 我真的需要吗? 这是做同一件事的“更好”方法吗? LoadImages
和LoadImages2
之间真的有任何区别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;
}
这里的术语和技术选择有些混乱。
以前,我只是按1个1个同步地加载它们。 异步加载它们可以更快地得到结果。
您的意思是串行与并发 ,而不是同步与异步 。 串行是一次一次,而并行是一次。 同步代码可以是串行或并发的,异步代码可以是串行或并发的。
其次, 并发与并行 。 Task.Run
是并行性的一种形式,它是通过向问题添加线程来实现并发的一种方式。 异步是一种通过释放线程来实现并发的方法。
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]);
}
PS:如果您决定使用(同步) LoadImages
,则需要修复一个争用条件,其中各种并行线程都将尝试不锁定字典就更新字典。
由于您坚持同步包裹电话,因此您可以尝试以下方法
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
异步。 常见的观察是,异步代码趋向于“感染”您的同步代码,最后您必须一直进行异步代码编写。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.