我的应用程序正在使用图像处理库来处理长时间运行的任务。具有设置和控件的主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无法实现这种行为,那么说它无法实现也将是一个公认的答案。
答案 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>