如何在WinForms中从子任务更新UI

15

我有一个简单的WinForms应用程序,通过TPL任务在线程上执行长时间运行的进程。在这个长时间运行的过程中,我想更新UI(例如进度条)。是否有一种方法可以在不需要使用.ContinueWith()的情况下完成这个操作?

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());


    }
}

执行此代码时,我收到了普遍异常:

跨线程操作无效:从创建它的线程以外的线程访问控件 'textBox1'。


我认为你必须将文本框引用粘贴到线程中。 - jwillmer
2个回答

20

是的,你可以在想要通信的窗口/控件上显式调用BeginInvoke。对于你的情况,代码如下:

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

1
那肯定是在路上了,但我遇到了编译错误:无法将 lambda 表达式转换为类型“System.Delegate”,因为它不是委托类型。 - devlife
我创建了一个Action,并传入该Action而非匿名方法。不确定为什么上述方法不起作用,但它已经让我接近成功了99%。感谢Drew。 - devlife
1
@devlife 哎呀,这是我的错,因为我太习惯只使用较新的API了。是的,由于旧的WinForms BeginInvoke API的签名设计方式,你需要显式地用新的Action(...)进行包装。我会更新我的答案。 - Drew Marsh
这正是我最终要做的。谢谢。 - devlife

5
你正在将TaskScheduler作为state(或者你称之为antecedent)传递,这没有太多意义。
我不确定你想要做什么,但是在启动Task时需要指定TaskScheduler,而不是在创建Task时。另外,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线程上执行。


这正是关键所在。我不想等到一个任务完成才执行另一个任务。 - devlife
我更新了我的示例,以更好地展示我想要表达的内容。 - devlife
在这种情况下,我的第一个示例仍然适用于您。我还更新了C# 5版本。 - svick
这基本上就是我想做的(只是没有做得很好)。迫不及待地想开始使用C#5了.... - devlife

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接