wlk
WPF MVVM死锁,界面卡死
1. 死锁的产生
MVVM模式下数据都是在View Model下更新的,数据会自动更新到界面。数据有时会来源与网络,网络接收数据一般都不在界面线程,网络线程接受到数据后,然后会更新View Model,再自动更新到界面。有的MVVM框架库在View Model更新数据时会以同步的方式更新界面,只有当界面更新完毕后View Model的更新才会返回,其内部会调用Dispatcher.Invoke函数或者Synchronizationcontext.Send函数。MVVM框架库Caliburn.Micro使用的就是Dispatcher.Invoke函数更新。这种同步更新界面的方式在某些情况下会产生死锁,如下代码:
上面的例子中可以看到网络线程锁定了viewmodel对象然后更新viewmodel对象,正在等待界面线程更新,而界面线程在等待viewmodel对象,而viewmodel对象已经被网络线程锁定,两个线程互相等待,从而造成死锁。
2. 解除死锁
方法一:
如果viewmodel数据最终更新还是在界面线程,则锁直接去除,以上例子种的两lock (viewmodel)完全可以直接去除,因为数据真正更新的位置都是在界面线程。
方法二:
网络线程更新完数据后解除锁,然后再更新界面线程如下:
方法三:
将同步更新方法更换成异步更新,使用Dispatcher.begininvoke函数或SynchronizationContext.Post函数,如下:
3. UI线程
在UI线程种会有一个消息循环,消息循环会不断的从消息队列中取出窗口消息,然后处理窗口消息。消息包括鼠标消息,键盘消息,计时器消息,用户自定义消息等等,我们使用Dispatcher.Invoke函数或者SynchronizationContext.Send函数则是直接让UI线程处理一个自定义消息处理完后才返回。而对于Dispatcher.BeginInvoke函数或SynchronizationContext.Post函数则只是将一个用户自定义消息发送到窗口队列,并不等待执行直接返回。
以下创建了一个UI线程并创建了一个窗口显示
myUIThread = new Thread(()=>
{
var syncCtx = new DispatcherSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(syncCtx);
Window w = new Window();
w.Width = 300; w.Height = 300;
w.Show();
Dispatcher.Run();//开启消息循环,线程会一直执行此函数
//var frame = new DispatcherFrame();
//Dispatcher.PushFrame(frame);
});
myUIThread.SetApartmentState(ApartmentState.STA);
myUIThread.isbackground = true;
myUIThread.Start();
UI线程必须为ApartmentState.STA模式,这种模式下某些资源是不能被其他线程访问,所以其他线程无法UI线程下的控件等。对于ApartmentState.MTA模式的线程,其资源是所有线程共享的,这种模式下线程运行效率更高,默认情况下创建的线程为ApartmentState.MTA模式。
4. 测试程序代码
ViewModel.cs
using System.componentModel; namespace MVVMTest { class ViewModel : INotifyPropertyChanged { private string msg; public string Msg { get { return msg; } set { msg = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Msg")); } } public event PropertyChangedeventhandler PropertyChanged; } } |
MainWindow.xaml.cs
using System; using System.Threading; using System.Threading.Tasks; using System.windows; using System.Windows.Threading; namespace MVVMTest { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { private ViewModel viewmodel; public MainWindow() { InitializeComponent(); viewmodel = new ViewModel(); this.DataContext = viewmodel; } private int count = 0; private void DeadLock_Click(object sender, RoutedEventArgs e) { Task t = Task.Run(() => //模拟网络线程更新数据 { lock(viewmodel) { Dispatcher.BeginInvoke((Action)delegate { this.viewmodel.Msg = $"Update Successuflly {++count}."; }); } }); Task.Delay(10).Wait(); lock (viewmodel) { //更新viewmodel的部分数据 } } private void NormalUpdate_Click(object sender, RoutedEventArgs e) { //方法一:直接去除锁,因为使用invoke数据只在UI现场使用,无需要加锁 //Task t = Task.Run(() => //{ // Dispatcher.Invoke(() => { this.viewmodel.Msg = $"Update Successuflly {++count}."; }); //}); //方法二:使用BeginInvoke,发送消息给UI线程执行。 Task t = Task.Run(() => { lock (viewmodel) { Dispatcher.BeginInvoke((Action)delegate{ viewmodel.Msg = $"Update Successuflly {++count}."; } ); } }); Task.Delay(10).Wait(); lock (viewmodel) { } } private void ShowMsg_Click(object sender, RoutedEventArgs e) { viewmodel.Msg = $"Hello."; } private Thread myUIThread = null; private void CreateUIThread_Click(object sender, RoutedEventArgs e) { if (null != myUIThread && myUIThread.IsAlive) { myUIThread.Abort(); } myUIThread = new Thread(()=> { var syncCtx = new DispatcherSynchronizationContext(); SynchronizationContext.SetSynchronizationContext(syncCtx); Window w = new Window(); w.Width = 300; w.Height = 300; w.Show(); Dispatcher.Run();//开启消息循环,线程会一直执行此函数 //var frame = new DispatcherFrame(); //Dispatcher.PushFrame(frame); }); myUIThread.SetApartmentState(ApartmentState.STA); myUIThread.IsBackground = true; myUIThread.Start(); } } } |
MainWindow.xaml
<Window x:Class="MVVMTest.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:MVVMTest" title="MainWindow" Height="380" Width="565"> <Grid> <TextBlock x:Name="textBlock" Margin="169,70,189,209" TextWrAPPing="Wrap" Text="{Binding Msg}" TextAlignment="Center" Height="40" Width="159"/> <Button x:Name="ShowMsg" content="ShowMsg" Margin="217,157,225,134" Height="28" Width="75" Click="ShowMsg_Click" /> <Button x:Name="deallock" Content="DealLock" Margin="132,198,310,93" Height="28" Width="75" Click="DeadLock_Click"/> <Button x:Name="NormalUpdate" Content="Normal Update" Margin="302,198,117,93" Height="28" Width="98" Click="NormalUpdate_Click"/> <Button x:Name="CreateUIThread" Content="Create UI Thread" Margin="218,268,220,53" Height="28" Click="CreateUIThread_Click"/> </Grid> </Window> |
相关阅读
在实际应用中,运营人员在编辑数据时不希望因不小心点击了浏览器的回退或刷新按钮导致花费了很长时间编辑的数据丢失。可以采用以
Windows自动弹出MSN中文网界面解决办法 原因:每当用户连接到网络时,Windows 会向微软的一个域名发送访问请求,访问结果作为网络连接
规则就是用来打破的?这完全取决于规则本身。在移动应用界面设计(后续简称:移动设计)的世界里,大家对美学、手势和动效的看法略有不同。
本人开始做毕设了,但老师说工具要有界面,所以就开始找python做界面的东西……之前做过C#的界面,脱拉拽很快界面就完成了,后来我查了下
目前一共建了7各表mainpost 主贴信息pm 短信存放路径prop道具信息retopiic 回帖信息systemset 系统设置topi