UI线程上的异步WPF多状态更新

时间:2016-02-04 13:42:27

标签: c# wpf asynchronous async-await

我希望从长时间运行的方法更新状态。通常我会使用调度程序回发到UI线程,但我对使用异步等待感到好奇。

为了简单起见:

创建一个窗口,添加一个按钮

<Button Name="ButtonWithCodeBehind" Height="25" Click="ButtonWithCodeBehindOnClick"/>

在onClick处理程序

后面添加一些代码
private async void ButtonWithCodeBehindOnClick(object sender, RoutedEventArgs e)
{
    await Task.Factory.StartNew(() =>
    {
        ButtonWithCodeBehind.Content = "First";
        Thread.Sleep(1000);
        ButtonWithCodeBehind.Content = "Second";
        Thread.Sleep(1000);
        ButtonWithCodeBehind.Content = "Third";
    });
}

这显然会破坏,因为会在错误的线程上访问ButtonWithCodeBehind.Content。

有没有办法让这项工作不做如下:

Deployment.Current.Dispatcher.BeginInvoke(()=>ButtonWithCodeBehind.Content = "Second");

这里的关键是长时间运行的任务会随着进展而生成更新,我可以将代码重构为:

private async void ButtonWithCodeBehindOnClick(object sender, RoutedEventArgs e)
{
    var scheduler = TaskScheduler.FromCurrentSynchronizationContext();
    await Task.Factory.StartNew(() =>
    {
        Task.Factory.StartNew(() => Thread.Sleep(1000))
            .ContinueWith(t => ButtonWithCodeBehind.Content = "First", scheduler)
            .ContinueWith(t => Thread.Sleep(1000))
            .ContinueWith(t => ButtonWithCodeBehind.Content = "Second", scheduler)
            .ContinueWith(t => Thread.Sleep(1000))
            .ContinueWith(t => ButtonWithCodeBehind.Content = "Third", scheduler);
    });
}

但这很难看。此外,如果您取出async并等待关键字并将其替换为Task.WaitAll,它仍将按预期执行。

注意:如果您想知道为什么我使用Thread.Sleep而不是Task.Delay,我实际上也在Silverlight中对此进行了测试,并且async等待支持并不是包括.Delay(或者至少不是我期望的那样)。

2 个答案:

答案 0 :(得分:2)

如果您可以将长时间运行的任务拆分为两个不同的长时间运行操作(例如上面示例中的两个Thread.Sleep),您可以自行等待每个长时间运行的任务。因此UI更新将在UI线程上执行。

private async void ButtonWithCodeBehindOnClick(object sender, RoutedEventArgs e)
{
  ButtonWithCodeBehind.Content = "First";
  await Task.Run(() => Thread.Sleep(1000));
  ButtonWithCodeBehind.Content = "Second";
  await Task.Run(() => Thread.Sleep(1000));
  ButtonWithCodeBehind.Content = "Third";
}

答案 1 :(得分:1)

需要等待的唯一部分是长时间运行的部分 - IO调用,或者在这种情况下,是CPU绑定的睡眠。

private async void ButtonWithCodeBehindOnClick(object sender, RoutedEventArgs e)
{
    ButtonWithCodeBehind.Content = "First";
    await Task.Factory.StartNew(() => Thread.Sleep());
    ButtonWithCodeBehind.Content = "Second";
    await Task.Factory.StartNew(() => Thread.Sleep());
    ButtonWithCodeBehind.Content = "Third";
}

Await捕获同步上下文,并确保方法的其余部分已注册到在具有相同上下文的线程上运行的延续。在WPF中,UI线程处理代码ButtonWithCodeBehindOnClick,因此,默认情况下,将负责await之后的方法调用的其余部分。

您可以通过在任务上配置await来覆盖此默认行为:

 await Task.Factory.StartNew(() =>
    Thread.Sleep()).ConfigureAwait(continueOnCapturedContext: false);

但是,您绝对不希望在WPF中执行此操作,因为线程池线程将尝试更新您的UI。