WPF MVVM死锁,界面卡死

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wlk1229/article/details/81351292

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>

 

 

猜你喜欢

转载自blog.csdn.net/wlk1229/article/details/81351292