Wpf UI ContinueWith 中的异常行为,当 Task 本身运行得太快时

Wpf UI bahaviour strangeness in ContinueWith, when Task itself works too fast

模拟错误的代码。

XAML:

    </Grid.RowDefinitions>
    <TextBlock Grid.Row="0"
                Grid.Column="0"
                Margin="5,5,5,0"
                FontSize="14"
                HorizontalAlignment="Right"
                Text="User:">
    </TextBlock>
    <TextBox  Grid.Row="0"
                Grid.Column="1"
                Margin="5,5,5,0"
                Name="_txtLogin" 
                Text="{Binding Login}"/>
    <Button Grid.Row="1"
            Grid.Column="1" 
            Content="Ok"
            Width="100" 
            VerticalAlignment="Center" 
            HorizontalAlignment="Center" 
            Click="AutentificateClick" 
            Padding="0,2,0,2" 
            IsDefault="True" 
            Name="_btnOk" 
            />
</Grid>

代码:

private Boolean _isFirstTime = true;

public Window2()
{
    InitializeComponent();
    DataContext = this;
}

private void AutentificateClick(Object sender, RoutedEventArgs e)
{
    _btnOk.IsEnabled = false;
    Cursor = Cursors.Wait;

    Task<Boolean>.Factory.StartNew(InitConnection).ContinueWith(t =>
    {
        if (!t.Result)
            return;

        // Emulate some work after connection's been established
        Thread.SpinWait(1000000000); (2)

        _btnOk.IsEnabled = true;
        Cursor = Cursors.Arrow;

    }, TaskScheduler.FromCurrentSynchronizationContext());
}

private Boolean InitConnection()
{
    Thread.SpinWait(10000000); (1)
    return true;

    if (_isFirstTime)
    {
        // Emulate some work to establish connection
        Thread.SpinWait(10000000); (1)
        _isFirstTime = false;
    }

    return true;
}

上面的代码工作正常。要模拟错误评论,请在 InitConnection 方法中使用这些字符串:

Thread.SpinWait(10000000); (1)
return true;

并多次点击确定按钮。现在你可以看到 Cursor 总是工作正常,它改变了。但是按钮的 IsEnabled 属性 并不经常起作用。我的意思是按钮保持 Enabled

是否又是一个 WPF 错误,或者有什么合理的解释吗?

.Net 4.0, Win 7

使用 TaskScheduler.FromCurrentSynchronizationContext() 将告诉您的任务在 UI 线程上执行。

当你调用 Thread.SpinWait(1000000000); UI 将变得无响应(如果您尝试做任何事情,例如拖动 window 等,您会注意到这一点),因此不能保证禁用按钮。

所以最初,我建议您删除 TaskScheduler.FromCurrentSynchronizationContext(),但这样做意味着您无法在任务结束时重新启用该按钮,因为您将不再位于UI 线程。如果您按如下方式修改 AutentificateClick 方法,我想您会发现它可以满足您的要求,并且 UI 将在任务执行时保持响应。

private void AutentificateClick(Object sender, RoutedEventArgs e)
{
    _btnOk.IsEnabled = false;
    Cursor = Cursors.Wait;         

    Task<Boolean>.Factory.StartNew(InitConnection).ContinueWith(t =>
    {        
         if (!t.Result)
             return;

         // Emulate some work after connection's been established
         Thread.SpinWait(1000000000);

         this.Dispatcher.Invoke((Action)(() =>
         {
             _btnOk.IsEnabled = true;
             Cursor = Cursors.Arrow;
         }));                                
    });        
}