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;
}));
});
}
模拟错误的代码。
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;
}));
});
}