从线程调用控件的方法时,操作调用丢失,没有异常抛出

时间:2018-03-23 00:52:43

标签: c# multithreading

我有这种奇怪的情况,我试图从中调用控件的方法 一个线程。这是非常简单的代码,但由于某种原因,如果InvokeRequired为true,则调用永远不会执行。它永远挂起,没有异常抛出。 下面是一个简化版本,一旦控件添加到父窗体,它也会挂起。如果它没有添加到表单中,则InvokeRequired为false并且工作正常。

MyControl _ctl;
Thread _thread;

void Main()
{
    _ctl = new MyControl
    {
        Dock = DockStyle.Fill
    };
    Form frm = new Form();
    frm.Controls.Add(_ctl);
    frm.Show();
    (_thread = new Thread(Test)).Start();
    //Thread.Sleep(2000);
    _thread.Join();
}

void Test()
{
    _ctl.Write("Hello...!");
}

class MyControl : TextBox
{
    int _inputStart = -1;

    public void Write(string value)
    {
        Action act = () =>
        {
            AppendText(value);
            _inputStart = SelectionStart;
        };

        if (InvokeRequired)
            Invoke(act, Array.Empty<object>()); // this is where line execution hangs 
        else
            act();
    }
}

我想了解它为什么会挂起来?以及实施此类方案的正确方法是什么?

修改 好的,为了回应@Sami Kuhmonen的评论,我制作了另一个示例代码作为WinForm小应用程序运行

using System;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;

namespace testThreads
{
    public class Form1 : Form
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components;
        private MyControl _ctl;
        private Button _button;
        private Thread _thread;

        public Form1()
        {
            InitializeComponent();
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                components?.Dispose();
                components = null;
            }
            base.Dispose(disposing);
        }

        private void TestThreading()
        {
            (_thread = new Thread(Test)).Start();
            _thread.Join();
        }

        void Test()
        {
            _ctl.Write("Hello...!");
        }

        private void InitializeComponent()
        {
            SuspendLayout();
            //
            // _ctl
            //
            _ctl = new MyControl
            {
                Multiline = true,
                Location = Point.Empty,
                Size = new Size(300, 175)
            };
            Controls.Add(_ctl);
            //
            // _button
            //
            _button = new Button
            {
                Location = new Point(0, 177),
                Size = new Size(60, 21),
                Text = "Test"
            };
            Controls.Add(_button);
            _button.Click += (s, e) => TestThreading();
            // 
            // Form1
            // 
            ClientSize = new Size(300, 200);
            Name = "Form1";
            Text = "Form1";
            ResumeLayout(false);
        }
    }

    class MyControl : TextBox
    {
        int _inputStart = -1;

        public void Write(string value)
        {
            Action act = () =>
            {
                AppendText(value);
                _inputStart = SelectionStart;
            };

            if (InvokeRequired)
                Invoke(act, Array.Empty<object>());
            else
                act();
        }
    }
}

现在,如果我不使用_thread.Join();代码执行正常。但在我的真实场景中,我并没有使用Thread.Join,而是等待WaitHandle,它是这样的:

using System;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;

namespace testThreads
{
    public class Form1 : Form
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components;
        private MyControl _ctl;
        private Button _button;
        private Thread _thread;
        private readonly AutoResetEvent _evt = new AutoResetEvent(false);

        public Form1()
        {
            InitializeComponent();
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                components?.Dispose();
                components = null;
            }
            base.Dispose(disposing);
        }

        private void TestThreading()
        {
            (_thread = new Thread(Test)).Start();
            _evt.WaitOne();
        }

        void Test()
        {
            _ctl.Write("Hello...!");
            // simulate a long running task
            Thread.Sleep(2000);
            _ctl.Write("Done");
            _evt.Set();
        }

        private void InitializeComponent()
        {
            SuspendLayout();
            //
            // _ctl
            //
            _ctl = new MyControl
            {
                Multiline = true,
                Location = Point.Empty,
                Size = new Size(300, 175)
            };
            Controls.Add(_ctl);
            //
            // _button
            //
            _button = new Button
            {
                Location = new Point(0, 177),
                Size = new Size(60, 21),
                Text = "Test"
            };
            Controls.Add(_button);
            _button.Click += (s, e) => TestThreading();
            // 
            // Form1
            // 
            ClientSize = new Size(300, 200);
            Name = "Form1";
            Text = "Form1";
            ResumeLayout(false);
        }
    }

    class MyControl : TextBox
    {
        int _inputStart = -1;

        public void Write(string value)
        {
            Action act = () =>
            {
                AppendText(value);
                _inputStart = SelectionStart;
            };

            if (InvokeRequired)
                Invoke(act, Array.Empty<object>());// hangs
            else
                act();
        }
    }
}

当然,这是一个样本,它不会是一个单一的线程。

所以我想我需要阻止并等到执行完毕,同时更新控件的UI。或者,如果可能存在其他非阻塞方法..

0 个答案:

没有答案