2012-03-12 20 views
10

我有一個簡單的小winforms應用程序,通過TPL任務在另一個線程上執行長時間運行的進程。在這個長時間運行的過程中,我想更新UI(進度條或其他)。有沒有辦法做到這一點,而不需要.ContinueWith()?如何從WinForms中的子任務更新UI

public partial class Form1 : Form 
{ 
    private Task _childTask; 

    public Form1() 
    { 
     InitializeComponent(); 

     Task.Factory.StartNew(() => 
     { 
      // Do some work 
      Thread.Sleep(1000); 

      // Update the UI 
      _childTask.Start(); 

      // Do more work 
      Thread.Sleep(1000); 
     }); 

     _childTask = new Task((antecedent) => 
     { 
      Thread.Sleep(2000); 
      textBox1.Text = "From child task"; 
     }, TaskScheduler.FromCurrentSynchronizationContext()); 


    } 
} 

執行此代碼,我得到了無處不在的例外:

Cross-thread operation not valid: Control 'textBox1' accessed from a thread other than the thread it was created on.

+0

我認爲你必須粘貼文本框引用的線程。 – jwillmer 2012-03-12 19:14:23

回答

10

是的,你可以顯式調用BeginInvoke上要與溝通的窗口/控件。在你的情況,這將是這樣的:

this.textBox.BeginInvoke(new Action(() => 
{ 
    this.textBox.Text = "From child task."; 
})); 
+0

這絕對看起來在路上,但我得到一個編譯錯誤:無法轉換lambda表達式鍵入'System.Delegate',因爲它不是一個委託類型 – devlife 2012-03-12 19:56:52

+0

我創建了一個操作,並在行動而不是匿名方法傳遞。不知道爲什麼上述不起作用,但它讓我有99%的方式。謝謝德魯。 – devlife 2012-03-12 20:07:55

+1

@devlife糟糕,這是我過去習慣於使用更新的API的過錯。是的,由於舊WinForms BeginInvoke API的簽名設計方式,您需要顯式包裝一個新的Action(...)。我會更新我的答案。 – 2012-03-12 21:00:35

5

你傳遞TaskSchedulerstate(或antecedent,你叫吧)。這沒有什麼意義。

我不確定你想要做什麼,但是當你開始Task時需要指定TaskScheduler,而不是在創建它時。此外,似乎childTask不必是一個字段:

var scheduler = TaskScheduler.FromCurrentSynchronizationContext(); 

Task childTask = null; 

Task.Factory.StartNew(
    () => 
    { 
     Thread.Sleep(1000); 
     childTask.Start(scheduler); 
     Thread.Sleep(1000); 
    }); 

childTask = new Task(
    () => 
    { 
     Thread.Sleep(2000); 
     textBox1.Text = "From child task"; 
    }); 

當然,這一切都將是用C#5和await容易得多:

public Form1() 
{ 
    InitializeComponent(); 

    StartAsync(); 
} 

private async void StartAsync() 
{ 
    // do some work 
    await Task.Run(() => { Thread.Sleep(1000); }); 

    // start more work 
    var moreWork = Task.Run(() => { Thread.Sleep(1000); }); 

    // update the UI, based on data from 「some work」 
    textBox1.Text = "From async method"; 

    // wait until 「more work」 finishes 
    await moreWork; 
} 

你不能使async構造函數,但您可以從它運行一個async方法。 「正在工作」不會在UI線程上運行,因爲它是通過Task.Run()明確啓動的。但由於StartAsync()是直接調用的,它在UI線程上執行。

+0

這就是要點。我不想等到一個任務完成後再執行另一個任務。 – devlife 2012-03-12 20:07:10

+0

我更新了我的示例以更好地展示我想要了解的內容。 – devlife 2012-03-12 20:11:23

+0

在這種情況下,我的第一個樣本仍然適用於您。我還更新了C#5版本。 – svick 2012-03-12 20:27:18