0

更新日期1:

更多详细信息:线程 1 和 2 必须持续处于活动状态。线程 1 正在更新其 GUI 并执行 HTTP POST。线程 2 将 HTTPListener 用于传入的 HTTP POST,并将该数据提供给线程 1。因此,GUI 需要显示当前的文本框值,并在线程 2 提供数据时更新。Servy 或其他方法是否允许两个线程同时工作?看来主线程正在等待线程 2 完成它的工作。然后它接受 prepWork 并使用它。我在 Servy 的示例中进行了编码,但找不到带有 Task 类的 Run() 定义。它的图书馆没有这种方法。我在 VS 2010 上使用 Net 4.0。有没有等效的方法可以使用?Start() 也没有编译,我知道你只能运行一次任务。

原始问题:

我已经测试了可以成功启动我的事件并在事件处理程序中更新我的 GUI 文本框的代码,如果事件在我理解的 UI 线程 1 中启动。当我尝试调用线程 1 方法 Fire() 从我的独立线程 2 方法 PrepareDisplay(), Fire() 被调用并依次触发事件。我输入了一些线程安全的调用代码(模仿 MSDN 教程中的 WinForms 线程安全),但事件处理程序仍然没有更新文本框。单步执行代码时,似乎 InvokeRequired 为 false。我的最终目标是将数据从线程 2 传递到 UI 线程 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
    }
}
4

1 回答 1

2

InvokeRequired当您的主线程仍在运行时,您就可以调用了Thread.Sleep。它甚至还没有达到创建消息循环的地步(这是一个 in 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课堂上。

于 2014-02-03T21:41:49.180 回答