日期:2014-05-18  浏览次数:20952 次

子线程异步回调的问题
C# code
    //主窗口
    public partial class frmMain : Form
    {
        private System.Windows.Forms.TreeView tvChapterSelector;[color=#339966]//初始化语句略[/color]
         OpenStoryFile osfOpenStoryFile = new OpenStoryFile();

        public frmMain()
        {
            InitializeComponent();
        }

        private void frmMain_Load(object sender, EventArgs e)
        {
            osfOpenStoryFile.OpenStoryFileComplete += new OpenStoryFile.OpenStoryFileCompleteHandle(osfOpenStoryFile_OpenStoryFileComplete);
            osfOpenStoryFile.DoOpenStoryFile(@"F:\小说\科幻\回归千年.txt");
        }

        void osfOpenStoryFile_OpenStoryFileComplete(System.Collections.ArrayList e)
        {
            for (Int32 I = ConstantVars.GlogbalConst.iZero; I < alStoryChapter.Count; I++)
            {
                tvChapterSelector.Nodes.Add((string)alStoryChapter[I] + string.Empty);[color=#FF0000]//这句报错
                //在该控件上执行的操作正从错误的线程调用。使用 Control.Invoke 或 Control.BeginInvoke 封送到正确的线程才能执行此操作。[/color]
            }
        }
    }

    //异步打开文件的类
    public class OpenStoryFile
    {
        public delegate void OpenStoryFileCompleteHandle(System.Collections.ArrayList e);

        public event OpenStoryFileCompleteHandle OpenStoryFileComplete;
        private event OpenStoryFileCompleteHandle OpenStoryFileCompletePretreat;

        System.Windows.Forms.Control ctrlTurnThread;
        System.Threading.Thread thrdOpenFile;
        object objForLock = new object();

        string StoryFileToOpen;

        public OpenStoryFile()
        {
            ctrlTurnThread = new Control();[color=#993300]//这里创建控件应该表示这个控件是在主线程创建的吧?[/color]
            this.OpenStoryFileCompletePretreat += new OpenStoryFileCompleteHandle(OpenStoryFile_OpenStoryFileCompletePretreat);
        }

        void OpenStoryFile_OpenStoryFileCompletePretreat(OpenStoryFile.OpenStoryFileArgs e)
        {
            if (ctrlTurnThread.InvokeRequired)
            {
                MessageBox.Show("ctrlTurnThread.InvokeRequired");[color=#800000]//但是,这句没有执行到[/color]
                OpenStoryFileCompleteHandle osfcpT = new OpenStoryFileCompleteHandle(OpenStoryFileCompletePretreat);
                ctrlTurnThread.Invoke(osfcpT, new object[] { e });
            }
            else
            {
                MessageBox.Show("ctrlTurnThread.InvokeRequired Treated");[color=#800000]//直接跑这来了。为什么?[/color]
                OpenStoryFileComplete(e);
            }
        }

        public void DoOpenStoryFile(string StoryFileToOpen)
        {
            this.StoryFileToOpen = StoryFileToOpen;

            thrdOpenFile = new System.Threading.Thread(new System.Threading.ThreadStart(OpenStoryFileAsyn));
            thrdOpenFile.Start();
        }

        private void OpenStoryFileAsyn()
        {
            lock (objForLock)
            {
                if (System.IO.File.Exists(StoryFileToOpen))
                {
                    try
                    {
                        System.Collections.ArrayList alStoryChapter = new System.Collections.ArrayList();
                        [color=#0000FF]//打开文件并作相应处理后,把内容填充到alStoryChapter[/color]
                    }
                    OpenStoryFileCompletePretreat(alStoryChapter);[color=#0000FF]//调用委托[/color]
                }
            }
        }
    }


------解决方案--------------------
调用InvokeRequired的时候,
先检查控件的句柄(一种Win32窗口句柄)是否已经创建了,你的情况下IsHandleCreated为假,因为该控件还没有初次显示。
如果句柄还没有创建,则尝试检查控件的父控件,而你的情况没有父控件(没有执行过Controls.Add( ctrlTurnThread )之类的语句)。
如果还没有找到,InvokeRequired就直接返回false了。

InvokeRequired需要一个窗口句柄来调用API以便知道该窗口所属的线程号,然后比较该线程号和当前线程号,就可以得出需不需要Invoke了。
该API就是GetWindowThreadProcessId()如果你感兴趣的话

------解决方案--------------------
在第一次走OpenSt