如何阻止我的 UI 线程锁定?
Posted
技术标签:
【中文标题】如何阻止我的 UI 线程锁定?【英文标题】:How do I stop my UI thread from locking up? 【发布时间】:2014-07-17 12:51:26 【问题描述】:尽管使用了应在单独线程上创建的任务,但我遇到了 UI 进程在我的应用程序中冻结的问题。
我有一个正在加载窗口中旋转的图像,它会根据需要显示/隐藏,这里是 XAML:
<Image Name="LoadingCog" Source="/Resources/Cog.png" Margin="0,37" Width="60" Height="60" HorizontalAlignment="Center" RenderTransformOrigin="0.5,0.5">
<Image.Resources>
<Storyboard x:Key="LoadingCogRotateStoryboard">
<DoubleAnimation
Storyboard.TargetName="LoadingCogRotateTransform"
Storyboard.TargetProperty="Angle"
By="10"
To="360"
Duration="0:0:7"
FillBehavior="Stop"
RepeatBehavior="Forever" />
</Storyboard>
</Image.Resources>
<Image.RenderTransform>
<RotateTransform x:Name="LoadingCogRotateTransform" Angle="0" />
</Image.RenderTransform>
</Image>
我的代码隐藏中还有一个事件处理程序,用于在窗口可见性更改时重新启动动画的窗口:
void LoadingScreenWindow_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
var storyBoard = (System.Windows.Media.Animation.Storyboard)LoadingCog.Resources["LoadingCogRotateStoryboard"];
if (IsVisible == true)
storyBoard.Seek(TimeSpan.Zero);
storyBoard.Begin();
else
storyBoard.Stop();
我有一个控制器,负责在加载窗口中显示/隐藏/更改消息,其方法如下:
internal void ShowLoadingScreen(string loadingMessage)
Application.Current.Dispatcher.Invoke((Action)(() =>
_viewModel.LoadingMessage = loadingMessage;
_loadingWindow.Show();
));
internal void HideLoadingScreen()
Application.Current.Dispatcher.Invoke((Action)(() =>
_loadingWindow.Hide();
));
在我的App.xaml.cs
文件中,我有类似于以下的代码来启动我的应用程序的加载过程:
Task.Factory.StartNew(() =>
LoadingScreenController.ShowLoadingScreen("Loading application, please wait..");
PerformNotSoIntensiveLoadingOperation();
HideLoadingScreen();
System.Windows.Application.Current.Dispatcher.Invoke((Action)(() =>
LoginWindow.Show();
));
LoadingScreenController.ShowLoadingScreen("Logging in, please wait..");
PerformIntensiveLoadingOperations();
LoadingScreenController.HideLoadingScreen();
System.Windows.Application.Current.Dispatcher.Invoke((Action)(() =>
MainWindow.Show();
));
, TaskCreationOptions.LongRunning);
我使用TaskCreationOptions.LongRunning
来确保我确实获得了一个新线程。
因为PerformNotSoIntensiveLoadingOperation()
中的代码运行时间不会很长(无论如何在我的电脑上),所以加载动画效果很好。
当我隐藏窗口并在PerformIntensiveLoadingOperations()
中运行更密集的操作然后我再次显示窗口时,动画会冻结。
如果我在密集操作完成后让窗口保持打开状态,动画会按预期继续。
为了澄清,流程如下:
显示加载屏幕 > 做一些工作 > 隐藏加载屏幕 > 显示另一个窗口 > 在关闭该窗口之前等待用户交互 > 再次显示加载窗口 > 做更多工作 > 再次隐藏加载窗口 > 显示主应用程序窗口
在Task
的线程(不涉及任何UI 代码)的do some more work
部分,我的动画冻结了。
我正在使用 .Net 4.0.3。
如果我删除对Application.Current.Dispatcher.Invoke
的调用,我会收到预期的跨线程冲突异常,告诉我调用线程不拥有我要访问的对象,因此我的代码肯定不会在 UI 线程上运行.
这是什么原因造成的,我该如何防止它发生?
【问题讨论】:
【参考方案1】:在您的Task
中,您正在尝试进行 UI 工作...我不相信您可以做到这一点。您无法从后台线程访问 UI 控件。您可能会发现是 UI 线程忙于渲染或通知并使忙指示器冻结。
明确地说,所有 UI 工作都在 UI 线程上完成。因此,如果您尝试从后台线程在 UI 中显示某些内容,您会得到标准错误,或者代码会自动将自身编组到 UI 线程。
我在大型 WPF 应用程序中有类似的设置,它也遇到了同样的问题。在从数据库中获取数据时,忙碌指示器显示正常,但是当应用程序开始在 UI 中呈现数据时,忙碌指示器会冻结。
我什至尝试使用动画 Gif 来解决这个问题,但当然它们不会在 WPF 应用程序中(自己)制作动画。编写了代码来为 Gif 中的帧设置动画后,我很失望地发现它也遇到了同样的问题。
【讨论】:
在我的情况下,唯一完成的 UI 工作是显示/隐藏窗口。之后,所有代码都处理调用 Web 服务和数据库工作。此外,我明确使用 Dispatcher 调用显示/隐藏方法(如在我的代码示例中),因为没有它(如预期的那样)我收到“调用线程不拥有此对象”错误。加载窗口非常简单,它有一个TextBlock
和一个Image
,其中显示了完整的代码。隐藏加载窗口后会显示其他窗口,到那时我不在乎它是否没有动画。
'但是当应用程序开始在 UI 中呈现数据时,忙碌指示器会冻结。' UI 线程在渲染 UI 的其余部分时无法为繁忙指示器设置动画...
@AwkwardCoder 到那时,加载窗口被隐藏了,我不介意它没有动画,因为它无论如何都被隐藏了。发生的事情是我正在显示加载屏幕,做一些工作,隐藏加载屏幕,显示另一个窗口,等待用户交互,关闭该窗口,显示加载窗口,做更多工作,再次隐藏它并显示主应用程序窗口。在任务线程(不涉及任何 UI 代码)上“做一些工作”和“做更多工作”期间,我的动画冻结了。
这是哪个版本的 .Net?
@AwkwardCoder 4.0。我将把它包括在我的问题中以上是关于如何阻止我的 UI 线程锁定?的主要内容,如果未能解决你的问题,请参考以下文章