WPF UI冻结 - UI线程冲突?

时间:2022-02-25 19:34:20

I'm trying to create an image slideshow effect with WPF.
The method to update the slideshow with a new image is called every few seconds in a Windows.Forms.Timer, and runs in it's own thread within a Task (as seen below).

我正在尝试使用WPF创建图像幻灯片效果。使用新图像更新幻灯片的方法在Windows.Forms.Timer中每隔几秒调用一次,并在任务中运行它自己的线程(如下所示)。

private void LoadImage()
    {
        Task t = Task.Run(() =>
        {    
           this.Dispatcher.BeginInvoke((Action)(() =>
           {
               TimeSpan delay = new TimeSpan(0, 0, 0, 0, 0);
               Fader.ChangeSource(image, BitmapFromUri(new Uri(compPath + oComps[nCount].name)), delay, delay);
               image.Visibility = System.Windows.Visibility.Visible;

               mediaElement.Stop();
               mediaElement.Close(); ;
               mediaElement2.Stop();
               mediaElement2.Close();
               mediaElement.Visibility = System.Windows.Visibility.Collapsed;
               mediaElement2.Visibility = System.Windows.Visibility.Collapsed;

               imageLoop.Interval = oComps[nCount].duration;

               nCount++;

               imageLoop.Start();
           }));
        });
    }

Simultaneously, there is a scrolling text banner across the bottom of the canvas in an overlay. This too is running in it's own thread, updating the UI through a Dispatcher.

Every few images, both the scrolling text and the slideshow will pause for a second or two, seemingly waiting for the image to load. This behaviour is unexpected as each element is in a seperate thread.


Could this be a conflict between the two Task threads updating the UI thread?
What could be causing this?

同时,在画布的底部有一个滚动的文本横幅。这也是在它自己的线程中运行,通过Dispatcher更新UI。每隔几个图像,滚动文本和幻灯片显示都会暂停一两秒,似乎在等待图像加载。此行为是意外的,因为每个元素都在一个单独的线程中。这可能是更新UI线程的两个Task线程之间的冲突吗?可能是什么导致了这个?

1 个解决方案

#1


6  

Your code to put work on another thread does not put the work on another thread. Your BeginInvoke is sending it back to the UI thread and all your work is being done there.

将工作放在另一个线程上的代码不会将工作放在另一个线程上。您的BeginInvoke将其发送回UI线程,您的所有工作都在那里完成。

Do the heavy work before you do the BeginInvoke call so the work actually happens on the background thread.

在执行BeginInvoke调用之前执行繁重的工作,以便工作实际发生在后台线程上。

private void LoadImage()
{
    Task t = Task.Run(() =>
    {    
       //I assume BitmapFromUri is the slow step.
       var bitmap = BitmapFromUri(new Uri(compPath + oComps[nCount].name);

       //Now that we have our bitmap, now go to the main thread.
       this.Dispatcher.BeginInvoke((Action)(() =>
       {
           TimeSpan delay = new TimeSpan(0, 0, 0, 0, 0);

           //I assume Fader is a control and must be on the UI thread, if not then move that out of the BeginInvoke too.
           Fader.ChangeSource(image, bitmap), delay, delay);
           image.Visibility = System.Windows.Visibility.Visible;

           mediaElement.Stop();
           mediaElement.Close(); ;
           mediaElement2.Stop();
           mediaElement2.Close();
           mediaElement.Visibility = System.Windows.Visibility.Collapsed;
           mediaElement2.Visibility = System.Windows.Visibility.Collapsed;

           imageLoop.Interval = oComps[nCount].duration;

           nCount++;

           imageLoop.Start();
       }));
    });

I suspect your banner is also not actually doing work on the other thread, you may want to look in to it.

我怀疑你的横幅实际上并没有在另一个线程上工作,你可能想要查看它。


A even better solultion if possible is rewrite BitmapFromUri to be async and not use threads at all.

如果可能的话,更好的解决方法是将BitmapFromUri重写为异步并且根本不使用线程。

private async Task LoadImageAsync()
{
   TimeSpan delay = new TimeSpan(0, 0, 0, 0, 0);      

   var bitmap = await BitmapFromUriAsync(new Uri(compPath + oComps[nCount].name);
   Fader.ChangeSource(image, bitmap), delay, delay);
   image.Visibility = System.Windows.Visibility.Visible;

   mediaElement.Stop();
   mediaElement.Close(); ;
   mediaElement2.Stop();
   mediaElement2.Close();
   mediaElement.Visibility = System.Windows.Visibility.Collapsed;
}

#1


6  

Your code to put work on another thread does not put the work on another thread. Your BeginInvoke is sending it back to the UI thread and all your work is being done there.

将工作放在另一个线程上的代码不会将工作放在另一个线程上。您的BeginInvoke将其发送回UI线程,您的所有工作都在那里完成。

Do the heavy work before you do the BeginInvoke call so the work actually happens on the background thread.

在执行BeginInvoke调用之前执行繁重的工作,以便工作实际发生在后台线程上。

private void LoadImage()
{
    Task t = Task.Run(() =>
    {    
       //I assume BitmapFromUri is the slow step.
       var bitmap = BitmapFromUri(new Uri(compPath + oComps[nCount].name);

       //Now that we have our bitmap, now go to the main thread.
       this.Dispatcher.BeginInvoke((Action)(() =>
       {
           TimeSpan delay = new TimeSpan(0, 0, 0, 0, 0);

           //I assume Fader is a control and must be on the UI thread, if not then move that out of the BeginInvoke too.
           Fader.ChangeSource(image, bitmap), delay, delay);
           image.Visibility = System.Windows.Visibility.Visible;

           mediaElement.Stop();
           mediaElement.Close(); ;
           mediaElement2.Stop();
           mediaElement2.Close();
           mediaElement.Visibility = System.Windows.Visibility.Collapsed;
           mediaElement2.Visibility = System.Windows.Visibility.Collapsed;

           imageLoop.Interval = oComps[nCount].duration;

           nCount++;

           imageLoop.Start();
       }));
    });

I suspect your banner is also not actually doing work on the other thread, you may want to look in to it.

我怀疑你的横幅实际上并没有在另一个线程上工作,你可能想要查看它。


A even better solultion if possible is rewrite BitmapFromUri to be async and not use threads at all.

如果可能的话,更好的解决方法是将BitmapFromUri重写为异步并且根本不使用线程。

private async Task LoadImageAsync()
{
   TimeSpan delay = new TimeSpan(0, 0, 0, 0, 0);      

   var bitmap = await BitmapFromUriAsync(new Uri(compPath + oComps[nCount].name);
   Fader.ChangeSource(image, bitmap), delay, delay);
   image.Visibility = System.Windows.Visibility.Visible;

   mediaElement.Stop();
   mediaElement.Close(); ;
   mediaElement2.Stop();
   mediaElement2.Close();
   mediaElement.Visibility = System.Windows.Visibility.Collapsed;
}