Прерывание тяжелого процесса

Автор Тема: Прерывание тяжелого процесса  (Прочитано 3742 раз)

0 Пользователей и 1 Гость просматривают эту тему.

Оффлайн makslАвтор темы

  • ADN OPEN
  • Сообщений: 21
  • Карма: 4
Давайте тут обсудим возможность прерывания какого-то тяжелого процесса.

Как я вижу, стандартными событиями типа button_click это сделать не получается.

Код - C# [Выбрать]
  1.        
  2. private void button1_Click(object sender, EventArgs e)
  3.         {
  4.             FamilyStat.isStop = true;
  5.             MessageBox.Show("pressed");
  6.             this.Close();
  7.         }
  8.  

сообщение из этого примера никогда не появляется.

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

Код - C# [Выбрать]
  1.         void ShowProgBar()
  2.         {
  3.  
  4.             f_progr.Show();
  5.         }
  6.  
  7.  
  8.         public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
  9.         {
  10.                 Thread secThr = new Thread(new ThreadStart(ShowProgBar));
  11.                 secThr.Start();
  12.  
  13.                 foreach (string st in familyFiles)
  14.                 {
  15.                     if (isStop) { isStop = false; return Result.Cancelled; }
  16.  
  17.                         // тяжелый цикл
  18.                  }
  19.  
  20.                 f_progr.Close();
  21.  
  22.         }
  23.  
  24.  

Какие есть идеи?

Оффлайн Александр Пекшев aka Modis

  • ADN Club
  • *****
  • Сообщений: 1658
  • Карма: 366
  • Отец modplus.org
    • ModPlus
Re: Прерывание тяжелого процесса
« Ответ #1 : 13-03-2018, 17:14:27 »
Вся проблема в том, что окно у вас работает в одном потоке, а тяжелый процесс происходит в другом потоке. Соответственно, поток тяжелого процесса не может узнать о том, что в окне произошло нажатие отмены. Примеры подобных решений можно найти для WPF-проектов, но как реализовать такое в Ревите - я пока не знаю. Не сталкивался и, если честно, работу с потоками до сих пор не понимаю ))
Так что тут я пас

Оффлайн makslАвтор темы

  • ADN OPEN
  • Сообщений: 21
  • Карма: 4
Re: Прерывание тяжелого процесса
« Ответ #2 : 13-03-2018, 17:18:02 »
Вся проблема в том, что окно у вас работает в одном потоке, а тяжелый процесс происходит в другом потоке. Соответственно, поток тяжелого процесса не может узнать о том, что в окне произошло нажатие отмены. Примеры подобных решений можно найти для WPF-проектов, но как реализовать такое в Ревите - я пока не знаю. Не сталкивался и, если честно, работу с потоками до сих пор не понимаю ))
Так что тут я пас

в тяжелом цикле проверяется переменная isStop, которая меняется в button_click формы, которая в другом потоке. по идее такая связь должна бы работать, но проблема в том, что даже button_click не срабатывает, messageBox не отображается.

Оффлайн Александр Пекшев aka Modis

  • ADN Club
  • *****
  • Сообщений: 1658
  • Карма: 366
  • Отец modplus.org
    • ModPlus
Re: Прерывание тяжелого процесса
« Ответ #3 : 13-03-2018, 17:20:03 »
в тяжелом цикле проверяется переменная isStop, которая меняется в button_click формы, которая в другом потоке. по идее такая связь должна бы работать, но проблема в том, что даже button_click не срабатывает, messageBox не отображается.
Все работает совсем не так, как вы себе представляете =)
Вопрос, кстати, интересный. Попробуем с коллегами что-нибудь

Оффлайн makslАвтор темы

  • ADN OPEN
  • Сообщений: 21
  • Карма: 4
Re: Прерывание тяжелого процесса
« Ответ #4 : 13-03-2018, 17:25:04 »
сейчас проверил так:

Код - C# [Выбрать]
  1.  
  2.         void ShowProgBar()
  3.         {
  4.             f_progr.Show();
  5.             System.Threading.Thread.Sleep(5000);
  6.  
  7.             isStop = true;
  8.         }
  9.  
  10.  

Т.е. создается новый поток, который показывает форму и через 5 сек меняет переменную isStop (которая в обычном варианте должна меняться в button_click).
Как и предполагалось, тяжелый цикл завершился.

Так что почти все работает именно так, как я себе представляю. :)
Проблема остается только в том, чтобы заставить обработать событие button_click во время работы цикла.

Оффлайн Виктор Чекалин

  • Administrator
  • *****
  • Сообщений: 694
  • Карма: 111
  • Skype: chekalin-v
Re: Прерывание тяжелого процесса
« Ответ #5 : 13-03-2018, 18:06:40 »
Я бы рекомендовал вместо Thread использовать класс Task
Это довольно удобная обертка над Thread Там же есть и способы отмены.
Общая идея в любом случае будет такая как и у вас. В цикле или в других нужных местах делается проверка, не было ли команды отмены.

Но тут будет проблема в другом. Форум то по Revit API. И если мы в этом цикле будем что то делать из другого потока, то ожидаемо рано или поздно получим либо ошибку, что нельзя работать из другого потока, либо Revit тупо вылетит. И это уже будет другая тема.

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13829
  • Карма: 1784
  • Рыцарь ObjectARX
  • Skype: rivilis
Re: Прерывание тяжелого процесса
« Ответ #6 : 14-03-2018, 00:19:43 »
Проблема остается только в том, чтобы заставить обработать событие button_click во время работы цикла.
Явно где-то не хватает Application.DoEvents(), чтобы приложение могло обработать другие события. И этот вызов похоже должен быть где-то внутри "тяжелого цикла".
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Виктор Чекалин

  • Administrator
  • *****
  • Сообщений: 694
  • Карма: 111
  • Skype: chekalin-v
Re: Прерывание тяжелого процесса
« Ответ #7 : 14-03-2018, 07:37:27 »
И если мы в этом цикле будем что то делать из другого потока, то ожидаемо рано или поздно получим либо ошибку, что нельзя работать из другого потока, либо Revit тупо вылетит.
Прошу прощения, только сейчас заметил, что в коде только прогресс бар вызывается из другого потока, а Revit API вызывается в основном потоке.

Я такую задачу успешно решил. Алгоритм примерно следующий.
1) Форма открывается в основном потоке
2) При открытии формы или при нажатии на кнопку на этой форме запускается некая команда, в которой выполняется длительный процесс. Этот длительный процесс запускаю с помощью Task в другом потоке.
3) Тут проблема в том, что запустив длительный процесс в другом потоке, нельзя вызывать команды Revit API. Поэтому в тот момент, когда в цикле нужно обратиться к методам Revit API, эти методы я вызываю из ОСНОВНОГО потока. Таким образом, получаются безопасные вызовы к методам Revit API.
4) Кнопка отмены доступна в тот момент, когда не вызываются методы Revit API.

Псевдокод примерно такой
Код - C# [Выбрать]
  1. // метод вызывается при нажатии на кнопку или при запуске формы и запускает долгий процесс
  2. private void btn1_OnClick()
  3. {
  4. Task longTask =
  5.                 new Task(DoSomething);
  6. // запустили долгую задачу в отдельном процессе
  7. longTask.Run();
  8. }
  9.  
  10. void DoSomething()
  11. {
  12.      foreach(var i in collection)
  13.     {
  14.            // тут делаем что то с помощью Revit API, но нужно делать это в ОСНОВНОМ потоке. CallRevitAPI вызывает методы в ОСНОВНОМ потоке
  15.           CallRevitAPI(()=> {doc.GetElement(id);})
  16.          
  17.           if (cancelled)
  18.               return;
  19.     }
  20. }
  21.  
  22. //кнопка Cancel доступна, так как долгая задача в другом потоке.
  23. void btnCancel_OnClick()
  24. {
  25.     cancelled = true;
  26. }
  27.  

Когда я реализовывал подобный подход, основная сложность была как правильно передать основной поток, находясь в другом потоке. Это тоже успешно решается. Я постараюсь набросать тестовый пример, выдрав из моего основного проекта.

Явно где-то не хватает Application.DoEvents()
Рискую начать холивар, но вроде как это bad practice

Оффлайн Александр Пекшев aka Modis

  • ADN Club
  • *****
  • Сообщений: 1658
  • Карма: 366
  • Отец modplus.org
    • ModPlus
Re: Прерывание тяжелого процесса
« Ответ #8 : 14-03-2018, 09:57:06 »
Пытались вчера сделать подобное - Ревит падает, хоть убей)
При попытках я нашел вариант, когда можно даже не использовать ни второй поток, ни Application.DoEvents():
Код - C# [Выбрать]
  1. using System;
  2. using System.Threading;
  3. using System.Windows;
  4. using Autodesk.Revit.Attributes;
  5. using Autodesk.Revit.DB;
  6. using Autodesk.Revit.UI;
  7.  
  8. namespace KRDimensions
  9. {
  10.     [Transaction(TransactionMode.Manual)]
  11.     public class Test : IExternalCommand
  12.     {
  13.         public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
  14.         {
  15.             ProgressWindow progressWindow = new ProgressWindow();
  16.             progressWindow.Show();
  17.             try
  18.             {
  19.                 // long process with progress window
  20.                 for (int i = 0; i < 10; i++)
  21.                 {
  22.                     Thread.Sleep(500);
  23.                     progressWindow.Dispatcher.Invoke(() => progressWindow.TbProgressText.Text = "progress: " + i);
  24.                     if(StopProcess)
  25.                         break;
  26.                 }
  27.             }
  28.             catch (Exception exception)
  29.             {
  30.                 MessageBox.Show(exception.Message);
  31.             }
  32.  
  33.             progressWindow.Dispatcher.Invoke(() => { progressWindow.Close(); });
  34.  
  35.             return Result.Succeeded;
  36.         }
  37.  
  38.         public static bool StopProcess { get; set; } = false;
  39.     }
  40. }
Само окно:
Код - XML [Выбрать]
  1. <Window x:Class="KRDimensions.ProgressWindow"
  2.        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.        SizeToContent="Manual" Width="350" ResizeMode="NoResize" Height="100"
  5.        WindowStartupLocation="CenterScreen">
  6.     <StackPanel Orientation="Vertical">
  7.         <TextBlock Text="Progress text:"></TextBlock>
  8.         <TextBlock x:Name="TbProgressText"></TextBlock>
  9.         <Button Content="Cancel" Name="BtCancel" Click="BtCancel_OnClick"></Button>
  10.     </StackPanel>
  11. </Window>
И Code behind окна:
Код - C# [Выбрать]
  1. using System.Windows;
  2.  
  3. namespace KRDimensions
  4. {
  5.     public partial class ProgressWindow
  6.     {
  7.         public ProgressWindow()
  8.         {
  9.             InitializeComponent();
  10.         }
  11.  
  12.         private void BtCancel_OnClick(object sender, RoutedEventArgs e)
  13.         {
  14.             Dispatcher.Invoke(() => { Test.StopProcess = true; });
  15.         }
  16.     }
  17. }

С виду это все работает так как надо - появляется окно в котором отображается прогресс (в примере просто текстом) и по кнопке "Отмена" долгий процесс прерывается. Самая главная проблема - окно не модальное и пользователь в этот момент может потыкать мышкой в Ревит.
Мой ответ не является вариантом решения. Вдруг просто кому пригодится )

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13829
  • Карма: 1784
  • Рыцарь ObjectARX
  • Skype: rivilis
Re: Прерывание тяжелого процесса
« Ответ #9 : 14-03-2018, 12:52:43 »
Цитата: Александр Ривилис от 13-03-2018, 23:19:43

    Явно где-то не хватает Application.DoEvents()

Рискую начать холивар, но вроде как это bad practice
Если есть лучшее решение, то я не против...
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Александр Пекшев aka Modis

  • ADN Club
  • *****
  • Сообщений: 1658
  • Карма: 366
  • Отец modplus.org
    • ModPlus
Re: Прерывание тяжелого процесса
« Ответ #10 : 15-03-2018, 12:12:31 »
В общем - решение нашли и все достаточно просто. И не нужны никакие Application.DoEvents(). Тестовый проект прикладываю.

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

Оффлайн makslАвтор темы

  • ADN OPEN
  • Сообщений: 21
  • Карма: 4
Re: Прерывание тяжелого процесса
« Ответ #11 : 20-03-2018, 09:49:58 »
навалились другие задачи, пока даже попробовать не могу. но идея хорошая. я чуть погуглил и нашел еще один вариант, народ предлагает использовать async-await:
https://ru.stackoverflow.com/questions/418461/%D0%A0%D0%B0%D0%B1%D0%BE%D1%82%D0%B0-%D1%81-%D0%BA%D0%BE%D0%BD%D1%82%D1%80%D0%BE%D0%BB%D0%B0%D0%BC%D0%B8-%D0%B8%D0%B7-%D1%84%D0%BE%D0%BD%D0%BE%D0%B2%D0%BE%D0%B3%D0%BE-%D0%BF%D0%BE%D1%82%D0%BE%D0%BA%D0%B0/418463#418463

но пока отложил этот вопрос, вернусь к нему попозже.