361*_*615 0 c# wpf multithreading mvvm
我有一个窗口:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<StackPanel Orientation="Horizontal" Background="{Binding BackgroundColor}">
<Button Content="Button1" Click="ButtonBase_OnClick"/>
</StackPanel>
</Window>
Run Code Online (Sandbox Code Playgroud)
而这个CodeBehind窗口:
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using WpfApplication1.Annotations;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
private Brush _backgroundColor;
public MainWindow()
{
InitializeComponent();
DataContext = this;
Background = Brushes.Orange;
}
public Brush BackgroundColor
{
get { return _backgroundColor; }
set
{
_backgroundColor = value;
OnPropertyChanged();
}
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
Background = Brushes.Yellow;
Thread.Sleep(5000);
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Run Code Online (Sandbox Code Playgroud)
所以基本上我单击一个按钮并更改背景颜色.除非我阻止UI线程,否则一切正常.
当我做某些事情,Background = Brushes.Yellow;Thread.Sleep(5000);我希望背景颜色改变,然后UI冻结.
但是目前UI似乎无法在冻结之前重新渲染自身,并且在Sleep()释放锁定后颜色会发生变化.
我试图使用Dispatcher,设置优先级,但行为似乎是相同的.
任何人都有想法如何设置Sleep()较低的优先级,所以UI会在进入睡眠状态之前完全更新?
PS给定的代码只是我真实情况的快速再现,我从WPF应用程序启动另一个WPF应用程序进程.
简短的回答是不要在UI线程中阻止.永远.
说起来容易做起来难,但是您可以使用一些工具:
仍然有效的老派方法是使用a DispatcherTimer.DispatcherTimer在后台延迟,当计时器关闭时,您将在UI线程中获得回调.进行更新非常方便.不幸的是,围绕这个的代码有点难看.
目前最好的做法是使用async和await将其用.NET 4.5引入的.你的回调看起来会改变这种方式:
private async void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
Background = Brushes.Yellow;
await Task.Delay(TimeSpan.FromSeconds(5));
Background = Brushes.Red;
}
Run Code Online (Sandbox Code Playgroud)
我添加了第二个背景更改,因此您知道它实际上等待而不会阻止您的应用.关于这种方法有几点需要注意:
void用于事件处理程序,因为那些是无法等待的.Task或Task<ReturnType>允许您以等待该方法以完成为好.await从UI线程调用时,在系统完成等待时返回UI线程.如果您希望在从处理程序返回完成之前更新UI,则需要使用await Dispatcher.Yield().例如:
private async void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
IsProcessing = true;
await Dispatcher.Yield();
// the UI is now showing that processing is going on
Message = await DoProcessing();
// update the other properties with the results
IsProcessing = false;
}
private async Task<string> DoProcessing()
{
// Simulate heavy lifting here:
await Task.Delay(TimeSpan.FromSeconds(5));
return "Done processing";
}
Run Code Online (Sandbox Code Playgroud)
通过这种变化,您可以看到即使我们定义DoProcessing()为返回a Task<string>,我们也不会直接处理任务.这是由编译器为我们完成的.该async关键字包装在你任务的返回值.当await调用准备好而不阻塞线程时,调用将解除结果.这是使用UI的一种非常方便的方式.
注意:原始问题对Thread.Sleep()行为感到困惑.该方法阻止正在运行的线程,直到完成所请求的毫秒数.如果明确调用Wait()任务对象,情况也是如此.该await关键字只是把书签中的代码,这样,当后台任务最终完成,我们可以拿起我们离开的地方.
Thread.Sleep()是一个阻塞调用,所以它会阻止它Dispatcher做任何事情,直到它完成.线程优先级是什么并不重要.