2010-07-27 60 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