Почему мое текстовое поле не обновляется потокобезопасными вызовами с использованием InvokeRequired?

Обновление1:

Подробнее: потоки 1 и 2 должны быть постоянно активны. Поток 1 обновляет свой графический интерфейс и выполняет HTTP POST. Поток 2 использует HTTPListener для входящих HTTP-сообщений POST и передает эти данные потоку 1. Таким образом, графический интерфейс должен отображаться с текущими значениями текстового поля и обновляться, когда поток 2 предоставляет данные. Позволит ли подход Servy или другой подход обоим потокам выполнять свою работу одновременно? Похоже, что основной поток ждет, пока поток 2 завершит свою работу. Затем он берет prepWork и работает с ним. Я кодировал пример Servy, но не смог найти определение для Run() с классом Task. В его библиотеке нет такого метода. Я использую Net 4.0 на VS 2010. Можно ли использовать эквивалентный метод? Start() тоже не скомпилировался, и я понимаю, что вы можете запустить задачу только один раз. Спасибо за любую дополнительную помощь, которой вы можете поделиться.

Оригинальный вопрос:

Я протестировал код, который успешно запускает мое событие и обновляет текстовое поле моего графического интерфейса в обработчике событий, если событие запускается в том, что я понимаю как поток 1 пользовательского интерфейса. Когда я пытаюсь вызвать метод Fire() потока 1 из мой независимый метод Thread 2 PrepareDisplay(), Fire() вызывается и, в свою очередь, запускает событие. Я добавил некоторый потокобезопасный код вызова (по образцу учебника MSDN по потокобезопасности в WinForms), но обработчик событий по-прежнему не обновляет текстовое поле. При выполнении кода кажется, что InvokeRequired имеет значение false. Моя конечная цель - передать данные из потока 2 в поток 1 пользовательского интерфейса и обновить текстовые поля новыми данными. Я не понимаю, почему потокобезопасный код не позволяет этого. Может ли кто-нибудь помочь мне понять это лучше, и что я пренебрег? Ниже приведен код:

Большое Вам спасибо,

namespace TstTxtBoxUpdate
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Aag_PrepDisplay aag_Prep1 = new Aag_PrepDisplay();

            Thread AagPrepDisplayThread = new Thread(new ThreadStart(aag_Prep1.PrepareDisplay));
            AagPrepDisplayThread.Start();

            while(!AagPrepDisplayThread.IsAlive)
                ;
            Thread.Sleep(1000);

            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new SetOperation());
        }
    }
}

namespace TstTxtBoxUpdate
{
    // Thread 1: UI
    public partial class SetOperation : Form
    {
        private string text;
        public event Action<object> OnChDet;

        delegate void SetTextCallback(string text);
        private Thread demoThread = null;

        public SetOperation()
        {
            InitializeComponent();
            OnChDet += chDetDisplayHandler;
        }

        public void FireEvent(Aag_PrepDisplay aagPrep)
        {
            OnChDet(mName);
        }

        private void chDetDisplayHandler(object name)
        {
            this.demoThread = new Thread(new ThreadStart(this.ThreadProcSafe));
            this.demoThread.Start();
        }

        private void ThreadProcSafe()
        {
            this.SetText("402.5");
        }

        private void SetText(string text)
        {
            if(this.actFreqChan1.InvokeRequired)
            {
                SetTextCallback d = new SetTextCallback(SetText);
                this.Invoke(d, new object[] { text });
            }
            else
            {
                this.actFreqChan1.Text = text;
            }
        }
    }
}

namespace TstTxtBoxUpdate
{
    // Thread 2: Data prepare
    public class Aag_PrepDisplay
    {
        #region Fields

        private Aag_PrepDisplay mAagPrep;

        #endregion Fields

        #region Properties

        public Aag_PrepDisplay AagPrepDisp;

        public Aag_PrepDisplay AagPrep
        {
            get { return mAagPrep; }
            set { mAagPrep = value; }
        }

        #endregion Properties

        #region Methods

        public void PrepareDisplay()
        {
            mAagPrep = new Aag_PrepDisplay();
            SetOperation setOp1 = new SetOperation();
            setOp1.FireEvent(mAagPrep);     // calls Thread 1 method that will fire the event
        }

        #endregion Methods
    }
}

person Kent    schedule 03.02.2014    source источник


Ответы (1)


Вы подходите к моменту вызова InvokeRequired, когда ваш основной поток все еще находится на Thread.Sleep. Он еще даже не дошел до создания цикла сообщений (который есть в Application.Run), поэтому нет цикла сообщений для Invoke, к которому можно было бы маршалировать вызов.

Здесь есть всевозможные проблемы. Вы создаете несколько экземпляров своей формы, тот, который вы показываете, и совершенно другую форму, для которой вы устанавливаете текст. Вы явно не собирались этого делать; вы хотите иметь единую форму, для которой вы устанавливаете текст.

Ваш основной поток не должен ждать, пока ваш первый поток не завершится. Скорее всего его вообще не должно быть. Если бы не тот факт, что ваш новый поток создает еще один новый поток, тот факт, что ваш основной поток блокируется до тех пор, пока второй поток не завершит работу, а второй поток не попытается упорядочить вызов основной поток, он обычно блокируется. Вам вообще не следует создавать здесь второй новый поток, но это случай двух ошибок, «отменяющих друг друга». Это предотвращает взаимоблокировку, но оба они по-прежнему неверны и препятствуют вашей способности найти рабочее решение.

Вы также не должны иметь Thread.Sleep в основном потоке. Я понятия не имею, какую цель это пытается достичь.

Если вы хотите просто начать длительную работу, прежде чем показывать первую форму, а затем обновить эту форму, когда получите результаты, вы делаете намного больше работы, чем нужно.

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

public class SetOperation  : Form
{
    private Label label;
    public SetOperation(Task<string> prepWork)
    {
        prepWork.ContinueWith(t =>
        {
            label.Text = t.Result;
        }, TaskScheduler.FromCurrentSynchronizationContext());
    }
}

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

[STAThread]
static void Main()
{
    Task<string> prepWork = Task.Run(() => DoWork());
    Application.Run(new SetOperation(prepWork));
}

private static string DoWork()
{
    Thread.Sleep(1000);//placeholder for real work
    return "hi";
}

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

person Servy    schedule 03.02.2014
comment
Серви, спасибо за помощь. Я отправил обновленную информацию на вышеупомянутый исходный вопрос с еще несколькими вопросами. Еще раз спасибо за дополнительные рекомендации, которыми вы можете поделиться. - person Kent; 04.02.2014
comment
@Kent До сих пор недостаточно ясно, что вы на самом деле пытаетесь сделать. Не зная конкретно, что должно произойти, я не могу сказать больше. - person Servy; 04.02.2014
comment
Извини, Серви. 1) Main() должен запускать 2 потока. 2) Поток 2 Aag_PrepDisplay() обрабатывает данные из входящих клиентских HTTP-запросов POST. Он принимает отправленные данные и представляет их для методов основного потока 1. 3) Основной поток 1 будет периодически выполнять HTTP-запросы POST на сервер. Эти POST будут содержать обновленные данные из потока 2. Эти данные также будут отображаться в графическом интерфейсе. 4) Это текущие задачи для обоих потоков. 5) Task.Run(() => DoWork()) не компилируется. В сообщении об ошибке говорилось, что Run не имеет определения. Класс задач, похоже, не имеет Run(). 6) Ваш подход запускает оба потока одновременно? Спасибо - person Kent; 04.02.2014
comment
@Kent Для этого не следует создавать какие-либо новые темы. Вы должны просто выполнять работу асинхронно. Если вы хотите периодически выполнять какой-либо код, Timer — эффективный способ справиться с этим. - person Servy; 04.02.2014
comment
Извините, я периодически получаю сообщения POST. Я случайно публикую. Могу ли я синхронно прослушивать, получать, подготавливать и отображать входящие данные, пока я асинхронно выполняю POST? Разве синхронное входящее событие не блокирует случайные сообщения POST? Я хочу обновить данные текстового поля, но не уверен, что таймер должен управлять им. Я думал, что входящее событие должно. Хотя это периодическое событие, разве я не должен обрабатывать его асинхронно? Не уверен, когда начнут поступать данные. Я подумал, что лучше обрабатывать входящие и исходящие задачи в отдельных потоках. Можете ли вы объяснить, как один поток может обрабатывать его одновременно и не блокировать задачи? спасибо - person Kent; 05.02.2014
comment
@Kent Вы должны делать все это асинхронно. Вы не должны синхронно блокировать любой из этих IO. Периодическое выполнение работы, казалось, подразумевало каждый X интервал времени, который был бы таймером. Если есть какое-то другое событие, указывающее, когда вы должны делать... что угодно, то это совершенно нормально. - person Servy; 05.02.2014