Взаимодействие с двумя потоками пользовательского интерфейса с использованием разных окон - PullRequest
0 голосов
/ 27 июля 2010

Мое приложение использует библиотеку обработки изображений для обработки длительных задач. Основной интерфейс с настройками и элементами управления реализован в WPF. Обработка изображений должна отображаться, а основной интерфейс должен оставаться отзывчивым. После нажатия кнопки «Процесс» в основном пользовательском интерфейсе создается новый поток, который создает новое окно WinForm для отображения обработанных изображений.

Перед многопоточностью пользовательский интерфейс зависал во время обработки, и прогресс отображался в WinForm для отображения изображений. Затем, когда обработка завершится, WinForm останется с изображением в нем. События добавляются в новую WinForm, которые позволяют панорамирование и масштабирование. Функциональность панорамирования и масштабирования работала правильно.

Из-за требований проекта стало очевидно, что для правильной работы он должен быть многопоточным.

Теперь с новым потоком окно WinForm создается, как и раньше, и изображение обрабатывается и отображается. Проблема в том, что когда этот метод завершен, поток завершается. Наличие выхода из потока означает, что если выделенные буферы изображения не освобождаются, то приложение выдает исключение. Чтобы это исправить, существует метод, который вызывается для освобождения всех выделений до выхода из потока. Это исправляет исключение и заставляет весь поток успешно выполняться, но это означает, что буфер отображения изображения и форма для его отображения освобождаются / удаляются, и поэтому нет времени, доступного для событий масштабирования и панорамирования.

Лучшее решение, чтобы сделать поток не выходящим, это сделать AutoResetEvent и сделать что-то подобное в конце потока обработки изображения.

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

AutoResetEvent запускается кнопкой на главном пользовательском интерфейсе, который убивает поток. Это работает так, чтобы изображение отображалось столько времени, сколько необходимо и явно уничтожено пользователем, однако оно не позволяет запускать события Click и Drag, необходимые для панорамирования и масштабирования изображения. Есть ли способ сделать так, чтобы поток не выходил, не имея вращающегося цикла while, который предотвращает запуск событий? Желаемая функциональность заключается в том, чтобы поток оставался активным, чтобы не требовалось освобождать выделения и выполнять панорамирование и масштабирование.

Несмотря на то, что решение может быть очевидно для кого-то с большим опытом работы с потоками, любая помощь будет принята, поскольку я новичок в многопоточных приложениях.

Спасибо

РЕДАКТИРОВАТЬ: Следует знать, что конечной целью является отображение постоянного потока кадров, которые обрабатываются таким образом, взятые из захвата кадров. Поэтому я не думаю, что это сработает, чтобы обрабатывать их отдельно в фоновом режиме, а затем отображать их в основном пользовательском интерфейсе, потому что будет необходим постоянный поток дисплеев, и это заблокирует основной пользовательский интерфейс.

РЕДАКТИРОВАТЬ: реальная цель вопроса не в том, чтобы найти лучший способ сделать что-то подобное. Вместо этого я спрашиваю, можно ли остановить новый поток, чтобы завершить события щелчка. Если такое поведение не может быть достигнуто с помощью System.Threading.Thread, то утверждение, что оно не может быть достигнуто, также будет приемлемым ответом.

Ответы [ 2 ]

0 голосов
/ 29 июля 2010

Если вы можете использовать новые параллельные классы и коллекции в C # 4.0, это довольно простая задача. Используя BlockingCollection , вы можете добавлять изображения из любого потока в коллекцию, а фоновый потребитель снимает изображения с этой коллекции и обрабатывает их. Эту фоновую обработку можно легко создавать и управлять (или отменять) с помощью Задачи из TaskFactory. Проверьте это простое приложение WPF для загрузки изображений и преобразования их в черно-белые, если есть изображения, которые нужно обработать, не блокируя пользовательский интерфейс. Он не использует два окна, но я думаю, что он демонстрирует концепции:

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 голосов
/ 27 июля 2010

Использование фонового рабочего для обработки изображения для панорамирования и масштабирования, передачи данных в backgroundworker.RunCompleted Event.Затем вы можете отобразить новое изображение в главном потоке пользовательского интерфейса без замедления или блокировки.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...