在线程内调用的方法以意外顺序执行(C#.net)

时间:2017-08-31 01:21:35

标签: c# multithreading program-flow

寻求一般帮助来理解这个程序流程: 在Windows窗体应用程序中,为什么当我在新线程中调用一个方法时,线程在继续执行之前不等待方法完成?

默认情况下,线程内调用的方法是异步执行的吗? (我希望程序在方法完成之前阻塞,而不必使用下面的Thread.Sleep行)。对下面“Thread.Sleep”行的评论可能有助于进一步澄清我的问题。 - 谢谢!

    private void button1_Click(object sender, EventArgs e)
    {
        //Call doStuff in new thread:
        System.Threading.Thread myThread;
        myThread = new System.Threading.Thread(new System.Threading.ThreadStart(doStuff));
        myThread.Start();
    }

    private void doStuff()
    {
        //Instantiate external class used for threadSafe interaction with UI objects:
        ThreadSafeCalls tsc = new ThreadSafeCalls();
        int indx_firstColumn = 0;

        //Loop 3 times, add a row, and add value of "lastRowAdded" to column1 cell.
        for (int a = 0; a < 3; a += 1)
        {
            tsc.AddGridRow(dataGridView1); //Call method to add a row to gridview:
            Thread.Sleep(1000); // Why does the execution of the line above go all crazy if I don't pause here? It's executing on the same thread, shouldn't it be synchronous? 
            tsc.AddGridCellData(dataGridView1,indx_firstColumn, tsc.lastRowAdded,tsc.lastRowAdded.ToString()); //Add value of "lastRowAdded" (for visual debugging)
        }

“ThreadSafeCalls”类的内容:

        public int lastRowAdded = -999;

    public void AddGridRow(DataGridView gv)
    {
        if (gv.InvokeRequired)
        {
            gv.BeginInvoke((MethodInvoker)delegate () 
            {
                lastRowAdded = gv.Rows.Add();
            });
        }
        else
        {
            lastRowAdded = gv.Rows.Add();
        }
    }

    public void AddGridCellData(DataGridView gv, int column, int rowID, string value)
    {
        //Thread safe:
            if (gv.InvokeRequired)
            {
                gv.BeginInvoke((MethodInvoker)delegate () { gv.Rows[rowID].Cells[column].Value = value + " "; });
            }
            else
            {
                gv.Rows[rowID].Cells[column].Value = value;
            }
    }

2 个答案:

答案 0 :(得分:-1)

来自搜索结果:

Control.BeginInvoke Method (System.Windows.Forms)

在创建控件的基础句柄的线程上异步执行委托。在线程

上异步执行指定的委托

来源:msdn。{/ p>上的Control.BeginInvoke Method

答案 1 :(得分:-1)

编辑:目前还不完全清楚“疯狂”是什么意思,但在盯着代码一段不健康的时间后,我意识到有一个竞赛窗口在循环中表现出来不睡觉的时候。我多次运行这个剥离版本来确认。为长时间编辑道歉,但没有细节,很难理解这个问题。

//Loop 3 times, add a row, and add value of "lastRowAdded" to column1 cell.
for (int a = 0; a < 3; a += 1)
{
    tsc.AddGridRow(dataGridView1); //Call method to add a row to gridview:
    Thread.Sleep(1000); // Why does the execution of the line above go all crazy if I don't pause here? It's executing on the same thread, shouldn't it be synchronous? 
    tsc.AddGridCellData(dataGridView1,indx_firstColumn, tsc.lastRowAdded,tsc.lastRowAdded.ToString()); //Add value of "lastRowAdded" (for visual debugging)
}

打破它:

  1. 在UI线程上安排添加行并更新lastRowAdded
  2. 睡1秒。抛弃这种情况会导致竞争显现。
  3. 传递lastRowAdded的值和字符串等同于myThread记住它,在UI线程上安排要更新的单元格。
  4. 重复3次。
  5. 您遇到此问题的原因是caching。基本上,当你忽略睡眠时myThread会看到陈旧版本的lastRowAdded,然后将过时的副本传递给AddGridCellData。此陈旧值传播到UI线程,通常会导致-999或其他一些不正确的行索引。你有时会得IndexOutOfRangeException,但并非总是如此。睡眠恰好给UI线程足够的时间将其缓存的值写回主内存,然后myThread读取更新的值。它似乎在某些运行中正常工作,而在其他运行中不正确,具体取决于操作系统调度线程的核心。

    要解决此问题,您需要删除睡眠并同步对ThreadSafeCalls内所有可变数据的访问权限。最简单的方法是使用锁。

    循环实施:

    for (int a = 0; a < 3; a += 1)
    {
        tsc.AddGridRow(dataGridView1);
        tsc.AddGridCellData(dataGridView1, indx_firstColumn);
    }
    

    TSC实施:

    class ThreadSafeCalls
    {
        private object syncObject = new object();
        private int lastRowAdded = -999;
    
        public int LastRowAdded
        {
            get {
                lock (syncObject) {
                    return lastRowAdded;
                }
            }
            set {
                lock (syncObject) {
                    lastRowAdded = value;
                }
            }
        }
    
        public void AddGridRow(DataGridView gv)
        {
            if (gv.InvokeRequired) {
                gv.BeginInvoke((MethodInvoker)delegate () {
                    LastRowAdded = gv.Rows.Add();
                });
            }
            else {
                LastRowAdded = gv.Rows.Add();
            }
        }
        public void AddGridCellData(DataGridView gv, int column)
        {
            if (gv.InvokeRequired) {
                gv.BeginInvoke((MethodInvoker)delegate () {
                    var lastRow = LastRowAdded;
                    gv.Rows[lastRow].Cells[column].Value = lastRow + " "; 
                });
            } else {
                var lastRow = LastRowAdded;
                gv.Rows[lastRow].Cells[column].Value = lastRow + " ";
            }
        }
    }
    

    原始答案:

      

    默认情况下,线程内调用的方法是异步执行的吗?

    当提到关于创建线程的ThreadStart目标方法的执行时,是;这是线程的重点。您可以使用其他线程来执行诸如在后台加载或执行其他处理而不阻塞主线程的操作。

      

    (我希望程序在方法完成之前阻止   无需使用下面的Thread.Sleep行。)

    BeginInvoke向UI线程调度程序添加一个方法,以便稍后在UI线程上调用,当它完成之前正在执行的操作时(即处理OS事件,执行预定的方法)在你添加的那个之前)。这是延迟执行,并在计划后立即返回,这就是它没有阻塞的原因。执行被推迟到UI线程的原因是大多数UI框架不是线程安全的。修改UI的多个线程会导致数据争用,从而产生各种各样的问题。

    另外,您应该避免创建线程以响应用户输入。线程是昂贵的资源,应该尽快创建(理想情况下在初始化时)。在您的示例中,每次单击按钮时都会创建一个线程,这非常慢。