0

在理解此程序流程方面寻求一般帮助:在 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;
            }
    }
4

2 回答 2

-1

从搜索结果:

Control.BeginInvoke Method (System.Windows.Forms)

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

来源: msdn 上的Control.BeginInvoke 方法

于 2017-08-31T02:50:34.987 回答
-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. lastRowAdded在 UI 线程上安排行的添加和更新。
  2. 睡1s。忽略这一点会导致比赛显现。
  3. 传递值和字符串等价lastRowAddedmyThread记住它,调度要在 UI 线程上更新的单元格。
  4. 重复 3 次。

您遇到这种情况的原因是由于缓存。本质上,当您离开 sleep 时,会myThread看到一个过时的版本lastRowAdded,然后将过时的副本传递给AddGridCellData. 这个过时的值会传播到 UI 线程,通常会导致 -999 或其他一些不正确的行索引。您应该IndexOutOfRangeException有时会得到一个,但并非总是如此。休眠恰好给 UI 线程足够的时间将其缓存值写回主内存,myThread然后读取更新的值。它似乎在某些运行上正常工作,而在其他运行上则不正确,具体取决于操作系统将线程安排在哪些内核上。

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

IE

循环实现:

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 的多个线程会导致数据竞争,从而产生各种问题。

附带说明一下,您应该真正避免创建线程以响应用户输入。线程是昂贵的资源,应该尽快创建(最好在初始化时)。在您的示例中,每次单击按钮时都会创建一个线程,这非常慢。

于 2017-08-31T02:51:08.850 回答