2012-03-12 65 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