2017-08-31 40 views
0

寻求一般帮助理解这个程序流程: 在窗户那为什么当我调用一个新的线程中的一个方法,该线程不构成应用在继续执行之前等待该方法完成?方法称为线程中意外顺序(C#.NET)执行

默认情况下异步执行的线程中调用的方法? (我希望程序阻塞,直到方法完成而不必使用下面的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; 
      } 
    } 
+0

你确实需要为我们提供[mcve]。我试图复制你的问题,但不能。代码中可能会出现一些我们无法看到的情况。 – Enigmativity

回答

-1

编辑:这不完全清楚你的意思是“去都疯了”,但在代码凝视的时候,我意识到一个不健康的量后,有睡觉的ISN时表现为循环比赛窗”使用。我跑了这个很多次的剥离版本来确认。为长时间编辑道歉,但没有细节,很难理解问题。

//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次。

您遇到此问题的原因归因于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的多个线程会导致数据竞争,从而产生各种各样的问题。

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

+0

你的回答是错误的。 **在线程中调用的方法_synchronously _ **。 – Enigmativity

+0

也许措辞把你扔了。线程在同一个线程中引用执行时是同步的,但对于其他线程则是***异步***。 –

+0

正确。 OP询问**“线程内调用的方法”**,所以答案是调用是**同步**。 – Enigmativity

-1

从搜索结果中:

​​

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

来源:Control.BeginInvoke Method MSDN上。