繁体   English   中英

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

[英]C# running async tasks in sync context

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

任务-从Internet加载位图。 以前,我只是按1个1个同步地加载它们。 异步加载它们可以更快地得到结果。 在下面,我举了两个示例,如何获取单个图像GetImageGetImageAsync 对于图像列表,我将使用LoadImagesLoadImages2

LoadImages将在异步中运行同步功能(全部同时(?)), LoadImages2将在异步中运行异步功能并产生相同的结果(?)。 我不完全了解的事情-在GetImageAsync await request.GetResponseAsync() 我真的需要吗? 这是做同一件事的“更好”方法吗? LoadImagesLoadImages2之间真的有任何区别LoadImages2

此刻,我想选择的GetImageLoadImages选项。 另外,我不想用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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM