0

我尝试使用多个 ContinueWiths 来更新 UI。所以我希望每个 ContinueWith Task 都会单独更新 UI,但真正发生的情况是,它们会互相等待并立即更新 UI。因此,即使您每个 ContinueWith 都按时运行,但 UI 仅在最后一个完成时更新。

Thread.Sleep 表示此上下文中的工作。

答案:StartNew is Dangerous by Stephen Cleary。博客文章的最后一部分几乎使用了与我相同的示例。

我编辑了帖子,回答了我自己的问题,〜并排。

编辑:在 Test1() StartNew 将在线程池上运行,因为没有任何特定的 TaskScheduler 集。所以“工作”将在线程池上运行,但“更多工作”和“停止”将在 GUI 线程上运行,因为有一个设置的上下文。

经验:

    private void Test1()
    {
        var context = TaskScheduler.FromCurrentSynchronizationContext();

        listBox1.Items.Add($"{DateTime.Now:HH:mm:ss} - Start");

        Task task = Task.Factory.StartNew(() =>
            {
                Thread.Sleep(1000);
                Task.Factory.StartNew(() =>
                {
                    listBox1.Items.Add($"{DateTime.Now:HH:mm:ss} - Work");
                }, CancellationToken.None, TaskCreationOptions.None, context);
            })
            .ContinueWith(_ =>
            {
                Thread.Sleep(2000);
                listBox1.Items.Add($"{DateTime.Now:HH:mm:ss} - More Work");
            }, context)
            .ContinueWith(_ =>
                {
                    Thread.Sleep(1000);
                    listBox1.Items.Add($"{DateTime.Now:HH:mm:ss} - stop");
                }, context);
    }

编辑:但是,如果“工作”在线程池上运行,为什么结果会有延迟?因为我错了。当我第二次或第 n 次运行测试应用程序时,您在下面看到的只是真实的。

如果你运行它,会发生以下情况:

13:32:00 - Start
--- 4 seconds later ---
13:32:01 - Work
13:32:03 - More Work
13:32:04 - stop

如果我第一次运行调试,它看起来像这样:

13:32:00 - Start
--- 1 second later ---
13:32:01 - Work
--- 3 seconds later ---
13:32:03 - More Work
13:32:04 - stop

编辑:我想,如果我第一次运行 Test1(),'Work' 真的在线程池上运行。但是第二次 TaskScheduler.FromCurrentSynchronizationContext() 也将适用于“工作”,因此它将在 GUI 线程上运行。那些 Thread.Sleeps 真正在 GUI 线程上运行,我无法移动表单。

所以我的第一个问题是,为什么 UI 没有单独更新?因为这是我所期望的。

如果我从每个 ContinueWith 开始一个单独的任务,我会得到所需的结果(单独的 UI 更新),但这看起来是错误的/丑陋的/复杂的,我感觉我没有正确使用 ContinueWith(用于 UI 更新)。

编辑:这个工作的原因是,因为我没有使用 ContinueWiths 的“上下文”,所以它们将在线程池上运行,而不是锁定 UI。新创建的任务将在 UI 上运行,因为这是它们设置的上下文。

    private void Test2()
    {
        var context = TaskScheduler.FromCurrentSynchronizationContext();

        listBox1.Items.Add($"{DateTime.Now:HH:mm:ss} - Start");

        Task task = Task.Factory.StartNew(() =>
            {
                Thread.Sleep(1000);
                Task.Factory.StartNew(() =>
                {
                    listBox1.Items.Add($"{DateTime.Now:HH:mm:ss} - Short Work");
                }, Task.Factory.CancellationToken, TaskCreationOptions.None, context);

            })
            .ContinueWith(_ =>
            {
                Thread.Sleep(2000);
                Task.Factory.StartNew(() =>
                {
                    listBox1.Items.Add($"{DateTime.Now:HH:mm:ss} - Long Work");
                }, Task.Factory.CancellationToken, TaskCreationOptions.None, context);
            })
            .ContinueWith(_ =>
                {
                    Thread.Sleep(1000);
                    Task.Factory.StartNew(() =>
                    {
                        listBox1.Items.Add($"{DateTime.Now:HH:mm:ss} - stop");
                    }, Task.Factory.CancellationToken, TaskCreationOptions.None, context);
                });
    }

结果:

13:32:00 - Start
--- 1 second later ---
13:32:01 - Work
--- 2 seconds later ---
13:32:03 - More Work
--- 1 second later ---
13:32:04 - stop

编辑:tl;博士:小心 StartNew 和 TaskScheduler.FromCurrentSynchronizationContext();

4

1 回答 1

3

正确的方法是使用 async/await,这将始终确保在任务返回后上下文在 UI 线程上继续,即使该任务在另一个线程上执行也是如此。

private async Task Test1()
{

    listBox1.Items.Add($"{DateTime.Now:HH:mm:ss} - Start");

    await Task.Run(HardWorkTakesLongTime);

    listBox1.Items.Add($"{DateTime.Now:HH:mm:ss} - Work");

    await Task.Run(HardWorkTakesLongTime);

    listBox1.Items.Add($"{DateTime.Now:HH:mm:ss} - More Work");

    await Task.Run(HardWorkTakesLongTime);

    listBox1.Items.Add($"{DateTime.Now:HH:mm:ss} - stop");
}

void HardWorkTakesLongTime(){
    Thread.Sleep(2000);
}


// From a button click event or something like it
btn_Click(){

    Test1();
}
于 2017-05-24T12:59:48.543 回答