2010-07-27 61 views
0

我的应用程序使用图像处理库来处理长时间运行的任务。带有设置和控件的主UI在WPF中实现。需要显示图像处理,并且主UI需要保持响应。在主UI中点击'进程'按钮会产生一个新的线程,它会创建一个新的WinForm窗口来显示处理过的图像。使用不同窗口与两个UI线程交互

在它是多线程之前,UI会在处理过程中挂起,进程将在用于显示图像的WinForm。然后,当处理完成时,WinForm将保留其中的图像。事件被添加到允许平移和缩放的新WinForm中。平移和缩放功能正常工作。

由于项目需要多线程才能正常工作,这变得很明显。

现在使用新线程创建WinForm窗口,并像以前那样创建图像并进行处理和显示。问题是,当这个方法完成时,线程退出。线程退出意味着如果分配的映像缓冲区未被释放,那么应用程序将抛出异​​常。为了解决这个问题,在线程退出前有一个方法叫释放所有的分配。这修复了异常并使整个线程成功执行,但这意味着图像显示缓冲区和显示它的窗体被释放/处置,因此缩放和平移事件没有时间可用。

使线程不退出的最佳解决方案是创建一个AutoResetEvent并在图像处理线程的末尾具有类似的内容。

while (!resetEvent.WaitOne(0, false)) { } 
threadKill(); // frees all allocations 

AutoResetEvent由杀死线程的主UI上的按钮触发。这可以使用户根据需要尽可能长时间地显示图像并将其杀死,但它无法触发使图像平移和缩放所需的单击和拖动事件。有没有办法让线程不会退出,而没有一个旋转的while循环来防止事件被触发?所需的功能是让线程保持活动状态,以便不必释放分配,并且可以实现平移和缩放。

尽管解决方案对于具有更多体验线程的人来说可能是显而易见的,但任何帮助都将被赞赏,因为我是多线程应用程序的新手。

由于

编辑:应当知道,最终目标是要显示的以这种方式从帧接收器采取处理的帧的恒定流。所以我不认为它会在后台单独处理它们,然后将它们显示在主UI中,因为需要持续显示流,这会锁定主UI。

编辑:问题的真正意图是不找到更好的方式来做类似的事情。相反,我问是否可以停止新线程退出,以便点击事件可以触发。如果System.Threading.Thread无法实现这种行为,那么说它无法实现也将是一个被接受的答案。

回答

0

如果您可以在C#4.0中使用新的并行类和集合,那么这是一项非常简单的任务。使用BlockingCollection <T>您可以将任何线索的图像添加到集合中,并让背景消费者从该集合中取出图像并对其进行处理。使用TaskFactory中的任务可以轻松创建和管理(或取消)此后台处理。查看这个简单的WPF应用程序来加载图像并将它们转换为黑白图像,只要有图像可以处理而不会阻塞UI。它不使用两个窗口,但我认为它体现了概念:

using System; 
using System.Collections.Concurrent; 
using System.Collections.ObjectModel; 
using System.ComponentModel; 
using System.Threading; 
using System.Threading.Tasks; 
using System.Windows; 
using System.Windows.Media; 
using System.Windows.Media.Imaging; 
using System.Windows.Threading; 
using Microsoft.Win32; 

namespace BackgroundProcessing 
{ 
/// <summary> 
/// Interaction logic for MainWindow.xaml 
/// </summary> 
public partial class MainWindow : Window, INotifyPropertyChanged 
{ 
    private readonly BlockingCollection<BitmapImage> _blockingCollection = new BlockingCollection<BitmapImage>(); 
    private readonly CancellationTokenSource _tokenSource = new CancellationTokenSource(); 
    private ImageSource _processedImage; 

    public MainWindow() 
    { 
     InitializeComponent(); 
     CancellationToken cancelToken = _tokenSource.Token; 
     Task.Factory.StartNew(() => ProcessBitmaps(cancelToken), cancelToken); 
     PendingImages = new ObservableCollection<BitmapImage>(); 
     DataContext = this; 
    } 

    public ObservableCollection<BitmapImage> PendingImages { get; private set; } 

    public ImageSource ProcessedImage 
    { 
     get { return _processedImage; } 
     set 
     { 
      _processedImage = value; 
      InvokePropertyChanged(new PropertyChangedEventArgs("ProcessedImage")); 
     } 
    } 

    #region INotifyPropertyChanged Members 

    public event PropertyChangedEventHandler PropertyChanged; 

    #endregion 

    private void ProcessBitmaps(CancellationToken token) 
    { 
     while (!token.IsCancellationRequested) 
     { 
      BitmapImage image; 
      try 
      { 
       image = _blockingCollection.Take(token); 
      } 
      catch (OperationCanceledException) 
      { 
       return; 
      } 
      FormatConvertedBitmap grayBitmapSource = ConvertToGrayscale(image); 
      Dispatcher.BeginInvoke((Action) (() => 
               { 
                ProcessedImage = grayBitmapSource; 
                PendingImages.Remove(image); 
               })); 
      Thread.Sleep(1000); 
     } 
    } 

    private static FormatConvertedBitmap ConvertToGrayscale(BitmapImage image) 
    { 
     var grayBitmapSource = new FormatConvertedBitmap(); 
     grayBitmapSource.BeginInit(); 
     grayBitmapSource.Source = image; 
     grayBitmapSource.DestinationFormat = PixelFormats.Gray32Float; 
     grayBitmapSource.EndInit(); 
     grayBitmapSource.Freeze(); 
     return grayBitmapSource; 
    } 

    protected override void OnClosed(EventArgs e) 
    { 
     _tokenSource.Cancel(); 
     base.OnClosed(e); 
    } 

    private void BrowseForFile(object sender, RoutedEventArgs e) 
    { 
     var dialog = new OpenFileDialog 
         { 
          InitialDirectory = "c:\\", 
          Filter = "Image Files(*.jpg; *.jpeg; *.gif; *.bmp)|*.jpg; *.jpeg; *.gif; *.bmp", 
          Multiselect = true 
         }; 
     if (!dialog.ShowDialog().GetValueOrDefault(false)) return; 
     foreach (string name in dialog.FileNames) 
     { 
      CreateBitmapAndAddToProcessingCollection(name); 
     } 
    } 

    private void CreateBitmapAndAddToProcessingCollection(string name) 
    { 
     Dispatcher.BeginInvoke((Action)(() => 
              { 
               var uri = new Uri(name); 
               var image = new BitmapImage(uri); 
               image.Freeze(); 
               PendingImages.Add(image); 
               _blockingCollection.Add(image); 
              }), DispatcherPriority.Background); 
    } 

    public void InvokePropertyChanged(PropertyChangedEventArgs e) 
    { 
     PropertyChangedEventHandler handler = PropertyChanged; 
     if (handler != null) handler(this, e); 
    } 
} 
} 

这将是XAML:

<Window x:Class="BackgroundProcessing.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.RowDefinitions> 
     <RowDefinition Height="40"/> 
     <RowDefinition/> 
    </Grid.RowDefinitions> 
    <Grid.ColumnDefinitions> 
     <ColumnDefinition Width="*"/> 
     <ColumnDefinition Width="3*"/> 
    </Grid.ColumnDefinitions> 
    <Border Grid.Row="0" Grid.ColumnSpan="3" Background="#333"> 
     <Button Content="Add Images" Width="100" Margin="5" HorizontalAlignment="Left" Click="BrowseForFile"/> 
    </Border> 
    <ScrollViewer VerticalScrollBarVisibility="Visible" Grid.Column="0" Grid.Row="1"> 
     <ItemsControl ItemsSource="{Binding PendingImages}"> 
      <ItemsControl.ItemTemplate> 
       <DataTemplate> 
        <Image Source="{Binding}"/> 
       </DataTemplate> 
      </ItemsControl.ItemTemplate> 
     </ItemsControl> 
    </ScrollViewer> 
    <Border Grid.Column="1" Grid.Row="1" Background="#DDD"> 
     <Image Source="{Binding ProcessedImage}"/> 
    </Border>  
</Grid> 

+0

谢谢你的广泛答案。然而,最有可能无法工作的原因有两个。第一个是我必须使用C#3.5,第二个是看起来这个代码处理图像本身的实际显示。为了获得所需的FPS,成像库必须显示图像缓冲区。这就是为什么介绍WinForm的原因,因为成像库需要传递一个窗口句柄作为参数。 虽然感谢 – Fultonae 2010-07-30 14:17:36

0

使用后台工作人员处理图像进行平移和缩放,将数据传递到backgroundworker.RunCompleted事件。然后,您可以在主UI线程中显示新图像,而不会减速或锁定。

+0

我认为是这样的。然而,应用程序需要(一旦图像采集卡交付)不断捕获图像,处理它们并将其显示为视频。 – Fultonae 2010-07-27 14:35:52