【问题标题】:Grabbing Image with WebRequest causes UI to freeze使用 WebRequest 抓取图像会导致 UI 冻结
【发布时间】:2017-01-22 05:15:12
【问题描述】:

我想每 3 秒从网络摄像机中抓取一张图像并将其放入图像控件(WPF 应用程序)中。

我正在使用此代码:

DispatcherTimer 定义

DispatcherTimer dispatcherTimer = new DispatcherTimer(TimeSpan.FromSeconds(3), DispatcherPriority.Background, DispatcherTimer_Tick, Application.Current.Dispatcher);

图片抓取代码

private void DispatcherTimer_Tick(object sender, EventArgs evA)
    {
        Dispatcher.Invoke(DispatcherPriority.Background,
         new Action(() =>
         {
             try
             {
                 BitmapFrame src;
                 var webRequest = (HttpWebRequest)WebRequest.Create(@"http://camera_ip/cgi-bin/video.cgi?msubmenu=jpg");
                 webRequest.Credentials = new NetworkCredential("user", "password");
                 webRequest.Proxy = null;

                 var response = (HttpWebResponse)webRequest.GetResponse();
                 var stream = response.GetResponseStream();
                 var streamReader = new StreamReader(stream);
                 src = BitmapFrame.Create(streamReader.BaseStream);

                 imageTelecamera.BeginInit();
                 imageTelecamera.Source = src;
                 imageTelecamera.EndInit();

              }
             catch (Exception ex)
             {
                 DoLogD($"Error: {ex.Message}");
             }
         }
        ));
    }

它工作正常,但是当我抓取图像时,UI 会冻结几毫秒。

为什么调度程序不能在不影响 UI 的情况下在后台工作?

【问题讨论】:

  • 在 DispatcherTimer 的 Tick 处理程序中调用 Dispatcher.Invoke 没有意义,因为在 UI 线程中已经调用了处理程序方法。除此之外,Invoke 不会在后台线程中调用任何东西。它做的相反:如果你从后台线程调用它,它会调用 UI 线程中的一个动作。
  • 您可能希望在后台线程中调用抓取代码(例如,通过System.Threading.Timer),并在调度程序操作中调用imageTelecamera.Source = src; 。还不清楚 BeginInitEndInit 应该在这里做什么,因为您似乎在 Image 控件上调用它们,这根本没有必要。

标签: wpf image user-interface httpwebrequest freeze


【解决方案1】:

你对调度员是什么的误解

调度程序允许您向 GUI 线程发送命令,这意味着当您的命令正在执行时,GUI 线程会暂停等待它完成。

Dispatcher 的正确用法是将线程特定的任务发送到正确的线程来执行它们,即您的控件有一个子列表,该列表只能从控件中安全地编辑,因此如果您需要添加或删除一个孩子你使用控件 Dispatcher 来获取控件以执行添加新 Item 命令

这是一个完整的例子

XAML:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplication1"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">

    <DockPanel>
        <Button x:Name="butStart" DockPanel.Dock="Top" Click="Button_Click">start</Button>
        <TextBlock x:Name="output"/>
    </DockPanel>
</Window>

背后的代码:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        timer.Interval = new TimeSpan(0, 0, 3);
        timer.Tick += Timer_Tick;
    }

    private void Timer_Tick(object sender, EventArgs e)
    {
        //start taking photo
        Task.Run(()=> TakePhoto() );
    }

    public DispatcherTimer timer { get; } = new DispatcherTimer();

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        if(timer.IsEnabled)
        {
            timer.Stop();
            butStart.Content = "Start";

            //this works as you are in the main thread
            output.Text += "Time Stopped" + Environment.NewLine;
        }
        else
        {
            timer.Start();
            butStart.Content = "Stop";
            output.Text += "Time Started" + Environment.NewLine;
        }
    }
    public async void TakePhoto()
    {
        //with out the dispatcher this would error as you are not in the Gui thread so have no access to the Text property
        output.Dispatcher.Invoke(()=>output.Text += "Take Photo" + Environment.NewLine);
        //Simulate a long running task
        await Task.Delay(5000);
        output.Dispatcher.Invoke(()=>output.Text += "Take Photo - Complete" + Environment.NewLine);
    }
}

现在应用于您的代码

private void DispatcherTimer_Tick(object sender, EventArgs evA)
{
    Task.Run() =>
     {
         try
         {
             BitmapFrame src;
             var webRequest = (HttpWebRequest)WebRequest.Create(@"http://camera_ip/cgi-bin/video.cgi?msubmenu=jpg");
             webRequest.Credentials = new NetworkCredential("user", "password");
             webRequest.Proxy = null;

             var response = (HttpWebResponse)webRequest.GetResponse();
             var stream = response.GetResponseStream();
             var streamReader = new StreamReader(stream);
             src = BitmapFrame.Create(streamReader.BaseStream);

             imageTelecamera.Dispatcher.Invoke(()=>
             {
                 imageTelecamera.BeginInit();//not actually required
                 imageTelecamera.Source = src;
                 imageTelecamera.EndInit();//not actually required
             });
          }
         catch (Exception ex)
         {
             DoLogD($"Error: {ex.Message}");
         }
     }
    ));
}

【讨论】:

  • 我已经尝试了您的建议,但仍然会出现几毫秒的 UI 冻结。我开始认为问题可能是资源分配(WebRequest、Stream、StreamReader)。
  • 如果您的系统正在为资源而苦苦挣扎,那么您无能为力,认为您可以尝试在 Visual Studio 之外运行代码,因为 VS 非常麻烦,如果您将高分辨率图像加载到那么图像源可能会导致问题,因为它必须立即处理和显示图像,您可能会从 BitmapImage 而不是 BitmapFrame 获得更好的性能,因为图像针对后台加载进行了优化
猜你喜欢
  • 2012-08-02
  • 2018-10-06
  • 1970-01-01
  • 1970-01-01
  • 2020-10-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多