【问题标题】:Interacting with two UI threads using different windows使用不同的窗口与两个 UI 线程交互
【发布时间】:2010-07-27 14:17:21
【问题描述】:

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

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

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

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

使线程不退出的最佳解决方案是创建一个 AutoResetEvent 并在图像处理线程的末尾有这样的东西。

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

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

即使解决方案对于线程处理经验丰富的人来说可能是显而易见的,但由于我是多线程应用程序的新手,因此我们将不胜感激。

谢谢

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

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

【问题讨论】:

    标签: c# wpf winforms multithreading


    【解决方案1】:

    如果您可以使用 C# 4.0 中的新并行类和集合,这将是一项非常简单的任务。使用 BlockingCollection 您可以将来自任何线程的图像添加到集合中,并让后台使用者从该集合中取出图像并处理它们。使用 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>
    

    【讨论】:

    • 感谢您的广泛回答。然而,这很可能无法工作有两个原因。第一个是我必须使用 C# 3.5,第二个是这段代码似乎处理了图像本身的实际显示。为了获得所需的 FPS,成像库必须显示图像缓冲区。这就是引入 WinForm 的原因,因为成像库需要传递一个窗口句柄作为参数。不过谢谢
    【解决方案2】:

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

    【讨论】:

    • 我考虑过这样的事情。然而,应用程序需要(一旦提供图像采集卡)不断地捕捉图像、处理它们,并像视频一样显示它们。
    猜你喜欢
    • 2016-11-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-30
    • 1970-01-01
    相关资源
    最近更新 更多