异步/等待下载图像时发生死锁

6
我正在开发一个Windows Phone 8.1应用程序。我有一个屏幕,其中包含新闻标题和缩略图列表。
首先,我正在进行异步http请求以获取JSON格式的新闻集合(满足NotifyTaskCompletion模式)。
NewsCategories = new NotifyTaskCompletion<ObservableCollection<NewsCategory>>(_newsService.GetNewsCategoriesAsync());

新闻类别:

public class NewsCategory : ObservableObject
{
    ...
    public string Title { get;  set; }
    public ObservableCollection<News> Items { get;  set; }
}

新闻:

public class News : ObservableObject
{
    ...
    public string Title { get; set; }
    public string ImagePath { get; set; }
}

到目前为止,它运作得非常完美。但是一旦我获取了ImagePath属性,我想下载并显示给定的图像。我在这里找到了异步解决方案:WP 8.1 Binding image from a http request - 这样当xaml获取图像路径时,它会调用一个转换器类(BinaryToImageSourceConverter),同时使用NotifyTaskCompletion模式。
问题出现在以下方法中:
private async Task<BitmapImage> GetImage(string path)
{
    HttpClient webCLient = new HttpClient();
    var responseStream = await webCLient.GetStreamAsync(path);
    var memoryStream = new MemoryStream();
    await responseStream.CopyToAsync(memoryStream);
    memoryStream.Position = 0;
    var bitmap = new BitmapImage();
    await bitmap.SetSourceAsync(memoryStream.AsRandomAccessStream());
    return bitmap;
}

当第一个await被调用时,调试器永远不会到达下一行,方法也永远不会返回。 string path变量具有适当的内容。
到目前为止,我已经尝试使用ConfigureAwait(false),但在我的情况下它不起作用。
我还发现在主题中:Deadlock while using async await,如下所示:
当您使用ConfigureAwait(false)时,您告诉程序您不关心上下文。它可以解决一些死锁问题,但通常不是正确的解决方案。正确的解决方案很可能是永远不要以阻塞方式等待任务,并始终异步。
我不知道我在哪里以阻塞方式处理了这些内容。什么可能是死锁的原因?
如果这完全是错误的方法,您是否知道任何更适合下载项目集缩略图的模式?
谢谢您的帮助。

更新:GetImage是如何调用的:就像主题中所述:WP 8.1从http请求绑定图像

public class WebPathToImage : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        if (value == null) return null;
        return new NotifyTaskCompletion<BitmapImage>(GetImage((String)value));
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    { throw new NotImplementedException(); }

    private async Task<BitmapImage> GetImage(string path)
    {
        using (var webCLient = new HttpClient())
        {
            webCLient.DefaultRequestHeaders.Add("User-Agent", "bot");
            var responseStream =  await webCLient.GetStreamAsync(path).ConfigureAwait(false);
            var memoryStream = new MemoryStream();
            await responseStream.CopyToAsync(memoryStream);
            memoryStream.Position = 0;
            var bitmap = new BitmapImage();
            await bitmap.SetSourceAsync(memoryStream.AsRandomAccessStream());
            return bitmap;  
        }
    }
}

并且在XAML中:

<Image 
DataContext="{Binding ImagePath, Converter={StaticResource WebPathToImage}}"
Source="{Binding Result}" 
Stretch="UniformToFill" 
Height="79" Width="79"/>

3
GetImage 是怎么被调用的?你是使用 .Result 或者 .Wait 阻塞等待它吗? - Yuval Itzchakov
我已经编辑了我的帖子来显示代码。 - K.A.
1
我没有直接看到任何可能出错的地方。如果您能制作一个小的、最小化的复现,那将会有所帮助。 - Yuval Itzchakov
由于我无法重现,请尝试使用var responseStream = webCLient.GetStreamAsync(path).Result;,然后报告是否仍在同一行中出现死锁问题? - Florian-Rh
2个回答

0

首先,如果您正在使用 Windows Phone 8.1,则建议使用命名空间 Windows.Web.Http 中的 HttpClient,而不是来自 System.Net.HttpHttpClient。一些原因在 this link 上有解释。

我的答案使用新的 Windows.Web.Http.HttpClient,所以您的GetImage 方法可以像这样:

        private async Task<BitmapImage> GetImage(string path)
        {
            using (var webCLient = new Windows.Web.Http.HttpClient())
            {
                webCLient.DefaultRequestHeaders.Add("User-Agent", "bot");
                var responseStream = await webCLient.GetBufferAsync(new Uri(path));
                var memoryStream = new MemoryStream(responseStream.ToArray());
                memoryStream.Position = 0;
                var bitmap = new BitmapImage();
                await bitmap.SetSourceAsync(memoryStream.AsRandomAccessStream());
                return bitmap;
            }
        }

我已经在一个示例 URL 上测试了这个方法,并且它在一个 Image 控件中显示正确。
其次,为什么不让 Image 控件处理下载 BitmapImage 而不使用转换器,这样你的 XAML 看起来像这样:
<Image
    Source="{Binding ImagePath}" 
    Stretch="UniformToFill" 
    Height="79" 
    Width="79" />

这样,你的ImagePath可以是来自互联网和本地资源的路径。

0

尝试将您的GetImage调用放入Task.Factory.Run中,以强制任务在另一个线程中执行。

同时注意您的Bitmap创建可能会出现问题,因为该位图不是在UI线程中创建的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接