WPF MVVM死锁,界面卡死

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>

 

 

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
WPF MVVM模式的登录界面代码可以分为视图(View)、模型(Model)和视图模型(ViewModel)三部分。 首先是视图(View)部分,该部分主要负责呈现应用程序用户界面,以及与用户之间的交互。下面是一个简单的登录视图的XAML界面代码: ```xaml <Window x:Class="WpfApplication1.LoginView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="LoginView" Height="200" Width="350"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <TextBlock Grid.Row="0" Grid.Column="0" Text="用户名:" VerticalAlignment="Center"/> <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Username, UpdateSourceTrigger=PropertyChanged}" Width="150" Margin="5"/> <TextBlock Grid.Row="1" Grid.Column="0" Text="密码:" VerticalAlignment="Center"/> <PasswordBox Grid.Row="1" Grid.Column="1" Password="{Binding Password, UpdateSourceTrigger=PropertyChanged}" Width="150" Margin="5"/> <CheckBox Grid.Row="2" Grid.Column="1" Content="记住密码" VerticalAlignment="Center" IsChecked="{Binding RememberMe, Mode=TwoWay}" Margin="5"/> <Button Grid.Row="3" Grid.ColumnSpan="2" Content="登录" Command="{Binding LoginCommand}" Width="100" Margin="5" HorizontalAlignment="Right"/> <TextBlock Grid.Row="4" Grid.ColumnSpan="2" Text="{Binding ErrorMessage}" Foreground="Red" Margin="5"/> </Grid> </Window> ``` 接着是模型(Model)部分,该部分主要定义应用程序的数据模型以及相关操作,例如存储用户名密码等信息,以及验证登录信息是否正确。下面是一个简单的登录模型的C#代码: ```csharp public class LoginModel { private readonly IDictionary<string, string> _users = new Dictionary<string, string> { {"user1", "password1"}, {"user2", "password2"}, {"user3", "password3"} }; public bool ValidateUser(string username, string password) { if (_users.ContainsKey(username) && _users[username] == password) { return true; } return false; } } ``` 最后是视图模型(ViewModel)部分,该部分主要完成视图(View)与模型(Model)之间的数据绑定和交互。下面是一个简单的登录视图模型的C#代码: ```csharp public class LoginViewModel : INotifyPropertyChanged { private readonly LoginModel _model = new LoginModel(); private string _username; public string Username { get { return _username; } set { if (_username != value) { _username = value; RaisePropertyChanged("Username"); } } } private string _password; public string Password { get { return _password; } set { if (_password != value) { _password = value; RaisePropertyChanged("Password"); } } } private bool _rememberMe; public bool RememberMe { get { return _rememberMe; } set { if (_rememberMe != value) { _rememberMe = value; RaisePropertyChanged("RememberMe"); } } } private string _errorMessage; public string ErrorMessage { get { return _errorMessage; } set { if (_errorMessage != value) { _errorMessage = value; RaisePropertyChanged("ErrorMessage"); } } } private RelayCommand _loginCommand; public RelayCommand LoginCommand { get { if (_loginCommand == null) { _loginCommand = new RelayCommand( () => { if (_model.ValidateUser(_username, _password)) { ErrorMessage = ""; MessageBox.Show("登录成功!"); } else { ErrorMessage = "用户名或密码错误!"; } }, () => { return !string.IsNullOrEmpty(_username) && !string.IsNullOrEmpty(_password); } ); } return _loginCommand; } } public event PropertyChangedEventHandler PropertyChanged; private void RaisePropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } } ``` 以上就是WPF MVVM模式的简单登录界面代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值