使用 ContinueWiths 链更新 UI
Update UI with chains of ContinueWiths
我尝试使用多个 ContinueWiths 来更新 UI。所以我希望每个 ContinueWith Task 都会单独更新 UI,但真正发生的是,他们互相等待并立即更新 UI。因此,即使每个 ContinueWith 运行 都准时,但 UI 仅在最后一个完成时更新。
Thread.Sleep 表示此上下文中的工作。
答案:StartNew is Dangerous by Stephen Cleary。博客的最后一部分 post 几乎使用了与我相同的示例。
我编辑了 post 并排回答我自己的问题。
编辑:在 Test1() 中,StartNew 将在线程池上 运行,因为没有设置任何特定的 TaskScheduler。所以'Work'会在线程池上运行,但是'More Work'和'stop'会在GUI线程上运行,因为有一个集合上下文。
exp:
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);
}
编辑:但是,如果线程池上有 'Work' 运行s,为什么结果会有延迟?因为我错了。当我第二次或第 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' 确实在线程池上 运行s。但是第二次 TaskScheduler.FromCurrentSynchronizationContext() 也将应用于 'Work',因此它将在 GUI 线程上 运行s。那些 Thread.Sleep 确实在 GUI 线程上 运行ing,我无法移动表格。
所以我的第一个问题是,为什么 UI 没有单独更新?因为这是我所期待的。
如果我从每个 ContinueWith 开始一个单独的任务,我会得到想要的结果(单独 UI 更新),但这看起来 wrong/ugly/complicated 我觉得我没有使用ContinueWith 正确(用于 UI 更新)。
编辑:之所以可行,是因为我没有将 'context' 与 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;dr:小心使用 StartNew 和 TaskScheduler.FromCurrentSynchronizationContext();
正确的方法是使用 async/await 这将始终确保上下文在任务 returns 之后在 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();
}
我尝试使用多个 ContinueWiths 来更新 UI。所以我希望每个 ContinueWith Task 都会单独更新 UI,但真正发生的是,他们互相等待并立即更新 UI。因此,即使每个 ContinueWith 运行 都准时,但 UI 仅在最后一个完成时更新。
Thread.Sleep 表示此上下文中的工作。
答案:StartNew is Dangerous by Stephen Cleary。博客的最后一部分 post 几乎使用了与我相同的示例。
我编辑了 post 并排回答我自己的问题。
编辑:在 Test1() 中,StartNew 将在线程池上 运行,因为没有设置任何特定的 TaskScheduler。所以'Work'会在线程池上运行,但是'More Work'和'stop'会在GUI线程上运行,因为有一个集合上下文。
exp:
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);
}
编辑:但是,如果线程池上有 'Work' 运行s,为什么结果会有延迟?因为我错了。当我第二次或第 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' 确实在线程池上 运行s。但是第二次 TaskScheduler.FromCurrentSynchronizationContext() 也将应用于 'Work',因此它将在 GUI 线程上 运行s。那些 Thread.Sleep 确实在 GUI 线程上 运行ing,我无法移动表格。
所以我的第一个问题是,为什么 UI 没有单独更新?因为这是我所期待的。
如果我从每个 ContinueWith 开始一个单独的任务,我会得到想要的结果(单独 UI 更新),但这看起来 wrong/ugly/complicated 我觉得我没有使用ContinueWith 正确(用于 UI 更新)。
编辑:之所以可行,是因为我没有将 'context' 与 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;dr:小心使用 StartNew 和 TaskScheduler.FromCurrentSynchronizationContext();
正确的方法是使用 async/await 这将始终确保上下文在任务 returns 之后在 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();
}