【问题标题】:WPF's Imagecontrol freezes the UIWPF 图像控件冻结 UI
【发布时间】:2013-10-22 23:07:07
【问题描述】:

我想在我的 WPF 应用程序中显示用户头像。这就是我绑定 Image-Control 的方式:

<Image Source="{Binding Path=Email, Converter={StaticResource GravatarConverter},IsAsync=True}">

GravatarConverter 返回给定电子邮件的 URL。不幸的是,这在加载第一个图像时完全阻止了我的 UI。请注意,我使用的是“IsAsync=True”。 经过一些研究,我发现在应用程序启动时在单独的线程中调用 FindServicePoint 时可以解决这个问题:

        Task.Factory.StartNew( () => ServicePointManager.FindServicePoint( "http://www.gravatar.com", WebRequest.DefaultWebProxy ) );

但是当我的应用程序已经在下载图像时 FindServicePoint 没有完成时,这不起作用。 有人可以解释一下为什么 WPF-App 需要这个 FindServicePoint,为什么会阻塞 UI 以及如何避免阻塞?

谢谢

更新:事实证明,在我取消选中 Internet Explorer 的“Internet 选项”->“连接”->“LAN 设置”中的“自动检测设置”后,我的问题就消失了。

我使用这个非常简单的 WPF 应用程序来重现问题,只需在文本框中插入图像的 url 并单击按钮。启用“自动检测设置”后,应用程序会在第一次加载图像时冻结几秒钟。使用此选项会立即禁用其加载。

MainWindow.xaml

<Window x:Class="WpfGravatarFreezeTest.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525">
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="Auto" />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <TextBox Grid.Column="0" Grid.Row="0" HorizontalAlignment="Stretch" x:Name="tbEmail" />
    <Button Grid.Column="0" Grid.Row="0" Click="buttonLoad_OnClick" HorizontalAlignment="Right">Set Source</Button>
    <Image x:Name="img" Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2" />
</Grid>        

MainWindow.xaml.cs

using System;
using System.Windows;
using System.Windows.Media.Imaging;

namespace WpfGravatarFreezeTest
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void buttonLoad_OnClick( object sender, RoutedEventArgs e )
        {
            try { this.img.Source = new BitmapImage(new Uri(this.tbEmail.Text)); }
            catch( Exception ){}            
        }
    }   
}

【问题讨论】:

    标签: c# wpf image servicepointmanager


    【解决方案1】:

    阻塞 UI 是因为 IsAsync=True 以异步方式运行,仅绑定进程。在您的情况下,您在转换过程中有一个长时间运行的操作。为了解决这个问题,您应该创建像这样异步呈现结果的转换器(基于this answer):

    创建任务完成通知器:

    public sealed class TaskCompletionNotifier<TResult> : INotifyPropertyChanged
    {
        public TaskCompletionNotifier(Task<TResult> task)
        {
            Task = task;
            if (task.IsCompleted) return;
            task.ContinueWith(t =>
            {
                var temp = PropertyChanged;
                if (temp != null)
                {
                    temp(this, new PropertyChangedEventArgs("Result"));
                }
            }); 
        }
    
        // Gets the task being watched. This property never changes and is never <c>null</c>.
        public Task<TResult> Task { get; private set; }
    
        // Gets the result of the task. Returns the default value of TResult if the task has not completed successfully.
        public TResult Result { get { return (Task.Status == TaskStatus.RanToCompletion) ? Task.Result : default(TResult); } }
    
        public event PropertyChangedEventHandler PropertyChanged;
    
    }
    

    创建实现 MarkupExtention 的异步转换器:

    public class ImageConverter: MarkupExtension, IValueConverter
    {
    
        public ImageConverter()
        {
        }
    
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value == null) return new BitmapImage();
            var task = Task.Run(() =>
            {
                Thread.Sleep(5000); // Perform your long running operation and request here
                return value.ToString();
            });
    
            return new TaskCompletionNotifier<string>(task);
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            return this;
        }
    
    }
    

    在 Xaml 中使用它:

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
    
        <TextBox x:Name="uri" Grid.Row="0" Text="{Binding ImageUri, ElementName=main}"/>
        <Image Grid.Row="1" DataContext="{Binding Text, ElementName=uri, Converter={local:ImageConverter}}" Source="{Binding Path=Result, IsAsync=True}"/>
    
    </Grid>
    

    更新 2 似乎 Image 控件本身异步加载图像。你是对的,第一次加载需要很多时间。你可以使用这样的代码:

        try
        {
            var uri = Uri.Text;
            var client = new WebClient();
            var stream = await client.OpenReadTaskAsync(uri);
            var source = new BitmapImage();
            source.BeginInit();
            source.StreamSource = stream;
            source.EndInit();
            Img.Source = source;
    
    
        }
        catch (Exception) { } 
    

    但它的性能并不比你的变种好。

    【讨论】:

    • 非常感谢。不幸的是,这仍然冻结了我的 UI。看起来我是否异步执行并不重要 - 问题似乎是这个 ServicePointManager.FindServicePoint 调用,它在第一次下载远程图像时每个应用程序执行一次。
    • 在我的情况下,所有作品都没有冻结。您在哪里调用 ServicePointManager.FindServicePoint?你能提供一些代码吗?还要注意我设置了 IsAsync=True。
    • 好吧,我发现了“错误”——但这真的很奇怪:它与 Internet Explorer 中的 Internet 设置有关。我在“设置”->“连接”->“局域网设置”中激活了“自动检测设置”。当我停用它时,一切都加载完美,当我激活它时,它会冻结。所以这似乎是一个操作系统问题?
    • 这真的很奇怪。我认为操作系统中没有这个问题。您是否像我在示例中所示的那样在 Task.Run 中执行 ServicePointManager.FindServicePoint 方法?如果您提供有问题的代码,我帮助您的机会就会增加。
    猜你喜欢
    • 2015-01-16
    • 1970-01-01
    • 1970-01-01
    • 2018-11-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-24
    • 1970-01-01
    相关资源
    最近更新 更多