WPF / XAML: как выполнить многопоточные процессы и предотвратить занятость / зависание основного интерфейса? - PullRequest
0 голосов
/ 12 февраля 2011

У меня есть приложение XAML, которое служит интерфейсом для автоматизации.Полная автоматизация может занять от 20 до 30 часов, поэтому я создал объект класса Task, который, по сути, оборачивает методы Thread (Пуск / Стоп / Сброс).

Однако, когда я запускаю метод автоматизации подОбъект задачи, пользовательский интерфейс XAML занят, и я не могу взаимодействовать с другими элементами управления, включая кнопку «Пауза», которая переключает флаг Thread.Set ().

Существует еще одно сообщение Запретить зависание пользовательского интерфейса без дополнительныхпотоки

, где кто-то порекомендовал класс BackgroundWorker, в этой статье MSDN упоминается, что это плохая идея использовать его, если он манипулирует объектами в пользовательском интерфейсе, что мое делает для отображения счетчиков состояния: http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx

Есть идеи по этому поводу?

    private void OnButtonStartAutomationClick(object sender, RoutedEventArgs e)
    {
        btnPauseAutomation.IsEnabled = true;
        Automation.Task AutomationThread = new Automation.Task(RunFullAutomation);
    }

    private void RunFullAutomation()
    {
        // do stuff that can take 20+ hours
        // threaded so I can utilize a pause button (block)
    }

class Task
{
    private ManualResetEvent _shutdownFlag = new ManualResetEvent(false);
    private ManualResetEvent _pauseFlag = new ManualResetEvent(true);
    private Thread _thread;
    private readonly Action _action;

    public Task(Action action)
    {
        _action = action;
    }

    public void Start()
    {
        ThreadStart ts = new ThreadStart(DoDelegatedMethod);
        _thread = new Thread(ts);            
        _thread.Start();
        _thread.Priority = ThreadPriority.Lowest;
    }

    public void Resume()
    {
        _pauseFlag.Set();
    }

    public void Stop()
    {
        _shutdownFlag.Set();
        _pauseFlag.Set();
        _thread.Join();
    }

    private void DoDelegatedMethod()
    {
        do
        {
            _action();
        }
        while (!_shutdownFlag.WaitOne(0));
    }
}

Ответы [ 4 ]

3 голосов
/ 12 февраля 2011

где кто-то порекомендовал класс BackgroundWorker, в этой статье MSDN упоминается, что это плохая идея использовать его, если он манипулирует объектами в пользовательском интерфейсе, что делает мой в целях отображения счетчиков состояний

BackgroundWorker на самом деле идеально подходит для этого, так как он был разработан для этого типа сценария. Предупреждение состоит в том, что вы не должны изменять элементы пользовательского интерфейса внутри DoWork, а скорее через ReportProgress и событие ProgressChanged.

Причина предупреждения заключается в том, что «DoWork» выполняется в фоновом потоке. Если вы установите значение элемента пользовательского интерфейса оттуда, вы получите исключение кросс-потоков. Однако ReportProgress / ProgressChanged автоматически перенаправляет вызов обратно в правильный SynchronizationContext для вас.

1 голос
/ 12 февраля 2011

Я бы не советовал развертывать свой собственный класс Task, учитывая, что .NET 4 полностью поддерживает асинхронный запуск задач в фоновом режиме с использованием библиотеки параллельных задач Тем не менее, вы можете делать то, что предлагает Рид, и использовать BackgroundWorker, который идеально подходит, или, если вы предпочитаете больше контролировать характер выполнения задачи, вы можете использовать класс Task из System.Threading.Tasks и реализовать что-то вроде этого:

public partial class MainWindow : Window
{
    CancellationTokenSource source = new CancellationTokenSource();
    SynchronizationContext context = SynchronizationContext.Current;
    Task task;
    public MainWindow()
    {
        InitializeComponent();
    }

    private void DoWork()
    {
        for (int i = 0; i <= 100; i++)
        {
            Thread.Sleep(500); //simulate long running task
            if (source.IsCancellationRequested)
            {
                context.Send((_) => labelPrg.Content = "Cancelled!!!", null);
                break;
            }
            context.Send((_) => labelPrg.Content = prg.Value = prg.Value + 1, null);
        }
    }

    private void Start_Click(object sender, RoutedEventArgs e)
    {
        task = Task.Factory.StartNew(DoWork, source.Token);
    }

    private void Cancel_Click(object sender, RoutedEventArgs e)
    {
        source.Cancel();
    }       
}

В DoWork() вы используете WPF SynchronizationContext и публикуете сообщения для обновления необходимого вам интерфейса пользователя.

В примере есть индикатор выполнения и элемент управления меткой, который обновляется на каждой итерации цикла for. Отмена поддерживается с помощью CancellationTokenSource, который проверяется на каждой итерации.

Надеюсь, это поможет.

1 голос
/ 12 февраля 2011

Здесь возможны две причины: во-первых, задача блокировки блокирует поток пользовательского интерфейса, а не работает в фоновом потоке, и, во-вторых, фоновый поток истощает поток пользовательского интерфейса, так что он никогда не получает возможности ответить для ввода. Вам нужно выяснить, какой из них имеет место. Грубый способ сделать это в вашем обработчике кликов Debug.WriteLine - идентификатор текущего потока (Thread.CurrentThread.ManagedThreadId) и сделать то же самое в обратном вызове RunFullAutomation.

Если они напечатают один и тот же номер, то у вас возникла первая проблема. Рид и TheZenker предоставили решения для этого.

Если они печатают разные числа, значит, вы уже в рабочем потоке и у вас вторая проблема. (BackgroundWorker может более элегантно перенести вас в рабочий поток и поможет обновить пользовательский интерфейс, но это не остановит голодание.) В этом случае простейшим решением, вероятно, является установка _thread.Priority = ThreadPriority.BelowNormal; перед запуском рабочего потока.

Между прочим, ваш код никогда не вызывает AutomationThread.Start, что означает, что обратный вызов RunFullAutomation даже не выполняется. Это просто опечатка?

1 голос
/ 12 февраля 2011

Посмотрите на объект Dispatcher в WPF. В вашем сценарии вы можете и должны запускать долгосрочные задачи в фоновом потоке, и BackgroundWorker - хороший способ сделать это. Когда вам нужно обновить пользовательский интерфейс, вам нужно проверить доступ к потоку пользовательского интерфейса, а если у вас его нет, используйте диспетчер для вызова метода обновления в потоке пользовательского интерфейса.

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