使用不同窗口与两个UI线程交互

时间:2010-07-27 14:17:21

标签: c# wpf winforms multithreading

我的应用程序正在使用图像处理库来处理长时间运行的任务。具有设置和控件的主UI在WPF中实现。需要显示图像处理,主UI需要保持响应。单击主UI中的“进程”按钮后,将生成一个新线程,该线程将创建一个新的WinForm窗口以显示已处理的图像。

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

由于项目的要求,它需要多线程才能正常运行。

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

使Thread不退出的最佳解决方案是创建一个AutoResetEvent,并在图像处理线程的末尾添加类似的东西。

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

AutoResetEvent由主UI上的一个按钮触发,该按钮杀死线程。这可以使图像显示只要需要并由用户显式杀死,但是它无法允许触发使图像平移和缩放所需的Click和Drag事件。有没有办法让线程不退出而没有旋转while循环,这可以防止事件被触发?所需的功能是使线程保持活动状态,以便不必释放分配,并且可以实现平移和缩放。

尽管解决方案对于具有更多线程经验的人来说可能是显而易见的,但是由于我是多线程应用程序的新手,因此可以获得任何帮助。

谢谢

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

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

2 个答案:

答案 0 :(得分:0)

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

答案 1 :(得分: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>