2012-11-29 83 views
1

我複製下面的例子Microsoft Thread Example簡單的線程問題

其中給出以下 的代碼,但我上線的錯誤「this.progressBar1.Value =的newval;」指出「跨線程操作無效:從其創建的線程以外的線程訪問的控制'progressBar1'」。

可能是什麼問題? 感謝 DAMO

C#代碼

public partial class Form1 : Form 
{   
    public Form1() 
    { 
     InitializeComponent(); 
    } 
    private void Form1_Load(object sender, EventArgs e) 
    { 
     Thread trd = new Thread(new ThreadStart(this.ThreadTask)); 
     trd.IsBackground = true; 
     trd.Start(); 
    } 
    private void ThreadTask() 
    { 
     int stp; 
     int newval; 
     Random rnd = new Random(); 
     while (true) 
     { 
      stp = this.progressBar1.Step * rnd.Next(-1, 2); 
      newval = this.progressBar1.Value + stp; 
      if (newval > this.progressBar1.Maximum) 
       newval = this.progressBar1.Maximum; 
      else if (newval < this.progressBar1.Minimum) 
       newval = this.progressBar1.Minimum; 
      this.progressBar1.Value = newval; 
      Thread.Sleep(100); 
     } 
    } 
    private void button1_Click(object sender, EventArgs e) 
    { 
     MessageBox.Show("This is the main thread"); 
    } 
} 
+4

UI控件不能從其他線程訪問,你將不得不調用它後面的UI線程上 –

+10

簡單:永遠不要從後臺線程觸摸UI(甚至讀值) 。並沒有像「簡單的線程問題」這樣的事情 - 這是一個矛盾的說法 –

+0

另外,非常重要的是:當後臺線程調用UI線程時,它應該儘快完成,以便UI線程可以保持響應。 –

回答

1

你必須調用新的委託:

delegate void ThreadTaskDelegate(); 
    private void ThreadTask() 
    { 
     if (this.InvokeRequired) 
     { 
      ThreadTaskDelegate del = new ThreadTaskDelegate(ThreadTask); 
      this.Invoke(del, null); 
     } 
     else 
     { 
      int stp; 
      int newval; 
      Random rnd = new Random(); 
      while (true) 
      { 
       stp = this.progressBar1.Step * rnd.Next(-1, 2); 
       newval = this.progressBar1.Value + stp; 

       if (newval > this.progressBar1.Maximum) 
        newval = this.progressBar1.Maximum; 
       else if (newval < this.progressBar1.Minimum) 
        newval = this.progressBar1.Minimum; 

       this.progressBar1.Value = newval; 

       Thread.Sleep(100); 
      } 
     } 


    } 

編碼愉快! :)

+1

你的例子永遠在UI線程中運行。 –

+1

這也是op原代碼也是。讓他編輯他的需求。 –

+0

微軟,但他們的網站上的「壞」代碼? – user1438082

1

這個例子是一個糟糕的例子。您必須訪問創建它們的線程中的控件。這幾乎總是主要的UI線程。 (可以爲不同的窗體分別創建自己的消息泵,但現在不用擔心)

在訪問控件之前,後臺線程必須使用Control.Invoke(Delegate)更改爲主UI線程。然後,當UI工作完成時,儘快離開UI線程。

例如:

所有的
private void ThreadTask() 
{ 
    // This code runs in the background thread. 
    while (true) 
    { 
     if (this.InvokeRequired) 
     { 
      // In order to access the UI controls, we must Invoke back to the UI thread 
      this.Invoke(new ThreadStart(SetRandomProgress)); 
     } 
     else 
     { 
      // We are already in the UI thread, so we don't have to Invoke 
      SetRandomProgress(); 
     } 

     // Wait briefly. This wait happens in the background thread. 
     // During this time, the UI is still responsive, because it is not blocked. 
     // You can verify this by tweaking the duration to something longer (say, 5000 ms). 
     Thread.Sleep(100); 
    } 
} 

private void SetRandomProgress() 
{ 
    Random rnd = new Random(); 
    int stp = this.progressBar1.Step * rnd.Next(-1, 2); 
    int newval = this.progressBar1.Value + stp; 
    if (newval > this.progressBar1.Maximum) 
     newval = this.progressBar1.Maximum; 
    else if (newval < this.progressBar1.Minimum) 
     newval = this.progressBar1.Minimum; 

    this.progressBar1.Value = newval; 
} 
2

首先,我強烈建議使用一些更高層次的技術,像Tasks而不是直接使用Thread類。任務類不僅更容易使用,而且更有效,更容易編寫,並且更容易避免您最近遇到的這些問題。

您試圖從非UI線程更新UI對象的代碼的主要問題。 UI技術(如Windows窗體或WPF)要求只有創建UI對象的線程才能訪問其屬性。

爲了解決這個問題,你應該把控制從非UI線程編組到UI線程。並有大量的選項要做到這一點(但它們都只是一個語法糖周圍的概念,稱爲SynchronizationContext):

  1. 使用直接同步方面:

// storing SynchronizationContext in the private field of your form 
private SynchronizationContext _syncContext = SyncrhonizationContext.Current; 

private void MethodFromTheSeparateThread() 
{ 
    // Marshaling control to UI thread 
    _syncContext.Post(d => 
      { 
       // Put all your code that access UI elements here 
      }, null); 
} 
  1. 使用InvokeRequired/Invoke作爲格雷戈爾已經提到

  2. 使用TaskScheduler.FromSynchronizationContext

private void ImplementLongRunningOperation() 
{ 
    int id; 
    string name; 
    Task.Factory.StartNew(() => 
    { 
    // our long-runing part. Getting id and name 
    id = 42; 
    name = "Jonh Doe"; 
    }).ContinueWith(t => 
    { 
     // Handling results from the previous task. 
     // This callback would be called in UI thread! 
     label1.Text = id.ToString(); 
     label2.Text = name; 
    }, TaskScheduler.FromSynchronizationContext); 
} 

正如我所提到的,最後一種方法(使用Tasks)是最好的方式,如果你在.NET 4.0以上的工作。這不僅可以幫助您從一些低級別課程中解脫出來,而且還可以帶來更清晰的設計,因爲您可以清楚地區分單獨的步驟,例如獲取數據並處理它們。

1

您可以像這樣重寫代碼,您的progressBar將在UI線程中更新,調用通過委託訪問progressBar的方法。檢查代碼:

private void Form1_Load(object sender, EventArgs e) 
     { 
      Thread trd = new Thread(new ThreadStart(this.ThreadTask)); 
      trd.IsBackground = true; 
      trd.Start(); 
     } 
     private void ThreadTask() 
     { 
      Random rnd = new Random(); 
      while (true) 
      { 
       int randValue = rnd.Next(-1, 2); 
       progressBar1.Invoke(new updater(UpdateProgressBar), new object[] {randValue}); 
       Thread.Sleep(100); 
      } 
     } 
     private delegate void updater(int value); 
     private void UpdateProgressBar(int randValue) 
     { 
      int stp = this.progressBar1.Step * randValue; 
      int newval = this.progressBar1.Value + stp; 
      if (newval > this.progressBar1.Maximum) 
       newval = this.progressBar1.Maximum; 
      else if (newval < this.progressBar1.Minimum) 
       newval = this.progressBar1.Minimum; 
      this.progressBar1.Value = newval; 

     }