Заставьте основной поток выполнять код при нажатии кнопки после form.show

У меня есть фрагмент кода, который выполняет некоторые вычисления, а затем вызывает команду form.show. Теперь у меня есть библиотека (revit api), которая не позволяет мне хранить переменные в проекте, не находясь в основном потоке.

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

form.Show(owner);
while(AppIsRunning){
    if(clicked)
        commit();
    else   
        Thread.sleep(100);
}

Однако, когда я это делаю, графический интерфейс загружается не полностью (черный фон, нет текста в дополнительных кнопках).

Я также пытался сделать это, используя метод вызова

    private void BtnOK_Click(object sender, System.EventArgs e)
    {
        Commit();
        Invoke(Commit);
    }

    private void Invoke(Action commit)
    {
        commit.Invoke();
    }

Однако это просто говорит мне, что это не основной поток, выполняющий функцию фиксации. Есть ли другой способ сделать это или я просто делаю ошибку.

Просто для ясности у меня есть команда form.show(owner), которая выдает ошибку, если она не выполняется основным потоком. У меня также есть функция commit(), которая должна быть оправдана основным потоком, иначе она выдает ошибку. Выполнение должно дождаться нажатия кнопки. Но основной поток, опрашивающий поток графического интерфейса для изменения, приводит к зависанию программы. Согласно моему поиску в Google, также возможно сделать что-то, связанное с внешним событием, чтобы вернуться в правильный контекст, но в приведенном примере использовалось использование python для вызова кода С#, есть ли хороший способ вызвать внешнее событие, чтобы вернуться в данный поток в С#?

Изменить: на основе некоторых предложений я создал следующий код:

public class ThreadManager
{
    static List<ThreadAble> orders = new List<ThreadAble>();
    public static bool running = false;
    public static void execute(ThreadAble action)
    {
        orders.Add(action);

    }

     static System.Timers.Timer timer;
    public static void RegisterAPIThreadAndHold(ExternalCommandData commandData)
    {

        UIApplication uiapp = commandData.Application;

        uiapp.Idling += Application_Idle;


    }


    private static void Application_Idle(Object o,IdlingEventArgs e)
    {
        if (orders.Count != 0)
        {
            ThreadAble f = orders.First();
            orders.Remove(f);
            f.execute();
        }
    }
}
public interface ThreadAble {
      void execute();        
}

Однако на самом деле это не запускается, когда я использую его в качестве общедоступного переопределения Result Execute (ExternalCommandData commandData, сообщение строки ссылки, элементы ElementSet)

   Form frm = new OverviewForm(ExternalCommandData commandData);
   frm.show()
    ThreadManager.RegisterAPIThreadAndHold(commandData);
    ThreadManager.Execute(new run_ThrowError())

где ThrowError.execute() — это Throw new Exception («это фактически выполняется»);


person Thijser    schedule 02.11.2016    source источник
comment
Я надеюсь, что это достаточно ясно, если у вас есть какие-либо вопросы, не стесняйтесь комментировать.   -  person Thijser    schedule 02.11.2016
comment
вы должны выполнять свою работу в другом потоке, а не в потоке пользовательского интерфейса.   -  person Daniel A. White    schedule 02.11.2016
comment
@DanielA.White Ну, у меня есть событие button.OnClick, к которому я могу прикрепить потоки, но, похоже, он использует поток графического интерфейса. Как мне перенести это в основной поток?   -  person Thijser    schedule 02.11.2016
comment
вы должны создать свой собственный поток или использовать один из пула потоков.   -  person Daniel A. White    schedule 02.11.2016
comment
@DanielA.White, как именно? Я имею в виду, что у меня есть основной поток, который должен быть тем, который вызывает поток графического интерфейса (в противном случае он генерирует исключение), а затем из потока графического интерфейса я хочу, чтобы основной поток снова выполнял некоторый код.   -  person Thijser    schedule 02.11.2016
comment
В Интернете есть множество шаблонов и примеров.   -  person Daniel A. White    schedule 02.11.2016
comment
Давайте продолжим обсуждение в чате.   -  person Thijser    schedule 02.11.2016
comment
Фреймворк имеет понятие SynchronizationContext, и есть WindowsFormsSynchronizationContext (неявный в контексте winforms), особенно подходящий для синхронизации потоков пользовательского интерфейса winforms, вам не нужно слишком много сворачивать свои собственные, потоки, менеджеры потоков и т. д. Это все по отношению к методам Invoke, BeginInvoke и т. д. класса Control. Пример здесь: codeproject.com/Articles/31971/   -  person Simon Mourier    schedule 11.11.2016


Ответы (3)


Ваш первый пример может работать, если вы замените Thread.Sleep на System.Windows.Forms.Application.DoEvents(). Это должно дать время для рисования графического интерфейса и не заморозить приложение полностью.

form.Show(owner);
while(AppIsRunning){
    if(clicked)
        commit();
    else   
    {
        System.Windows.Forms.Application.DoEvents();
        // Thread.sleep(100);
    }
}

Но это не идеальное решение для достижения этой цели. Лучше было бы вызвать команду Dispatcher.Invoke внутри вашего диалога для выполнения операций MainThread. Вы можете использовать, например, библиотеку GalaSoft — см. документацию и примеры объекта DispatcherHelper.

person smartobelix    schedule 16.11.2016

Я знаю два способа сделать это с помощью внешнего события или события бездействия.

С событием бездействия вы зарегистрируете его, и пока оно зарегистрировано, ваш код (в основном потоке) будет получать обратный вызов от Revit каждый раз, когда он не занят чем-то другим. Часто несколько раз в секунду.

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

Внешнее событие работает аналогично с точки зрения регистрации, но вы можете запросить триггер обратного вызова.

У Джереми Таммика должно быть 20 сообщений на сайте thebuildingcoder.typepad.com о немодальном диалоговом окне / материалах Revit.

person Matt    schedule 03.11.2016
comment
Можете ли вы привести пример того, как использовать это на внешнем событии или с событием бездействия? Я видел сообщение на thebuildingcoder.typepad.com, однако я не уверен, что понял его, поскольку его пример кода, похоже, предназначался для другого языка darenatwork.blogspot.nl/2015/06/ - person Thijser; 04.11.2016

Простое решение этой проблемы см. в образце приложения Revit SDK ModelessDialog ModelessForm_ExternalEvent. Он демонстрирует именно то, что вы просите.

person Jeremy Tammik    schedule 09.11.2016
comment
Я посмотрел ваш код в примере на вашем сайте (по крайней мере, я надеюсь, что получил правильный). И настройте что-то вроде этого: public static void RegisterAPIThreadAndHold(ExternalCommandData commandData) { UIApplication uiapp = commandData.Application; uiapp.Idling += Application_Idle; } Однако это не реализует никаких идей (я обновлю свой вопрос) - person Thijser; 09.11.2016