2017-08-31 110 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上。