我有一个关于MVVM实践的基本问题,不建议在viewmodel属性的setter中调用模型,该属性绑定到UI控件。
public int Woo
{
get
{
return _Woo;
}
set
{
_Woo=model.SetWoo(value);
NotifyPropertyChange("Woo");
}
}
相反,人们建议在模型中设置属性并向viewmodel发送事件以使用新值刷新其属性并最终刷新UI控件。因为如果模型的方法花费大量时间,上述代码将阻止UI控件。
我的问题是,即使我从模型中发送事件,UI线程仍然被阻塞,直到执行其订阅的委托完成。那么这两种方法有什么区别,哪一种是正确的?
答案 0 :(得分:4)
我总是使用上面的方法来阻止UI线程。
public int Woo
{
get
{
_Woo;
}
set
{
BackgroundWorker backgroundWorker = new BackgroundWorker();
//DoWork is ran on separate thread and does not block UI
backgroundWorker.DoWork += (sender, arguments) =>
{
arguments.Result = model.GetWoo(); //Store data in worker result
}
//This method runs definitely on UI
backgroundWorker.RunWorkerCompleted += (sender, arguments) =>
{
//Get stored result and assign to UI-bound stuff
_Woo = arguments.Result;
NotifyPropertyChange("Woo");
}
backgroundWorker.RunWorkerAsync();
}
}
答案 1 :(得分:2)
MVVM关注的是关注点的分离,它没有描述线程如何在事件和事件处理方面起作用。在UI线程中执行代码是不可避免的,诀窍是只在UI线程中执行快速操作,如果某些事情要花费超过50ms,那么作为程序员/开发人员,你有责任剥离一个新线程(或线程抽象,例如Task
)来执行任务并在操作完成时通知UI线程。
答案 2 :(得分:1)
理想情况下,您希望在与UI线程不同的线程上尽可能多地进行重要操作。然后,您可以使用某种策略重新连接到UI线程,以应用繁重的结果。
我发现最简单的方法是使用带有延续的Task,使用2个不同的TaskScheduler。我认为有一种更简单的方法可以使用async / await,但是我对它不熟悉,无法对任何权限进行评论。
使用您的示例:
private int? _Woo;
public int? Woo
{
get
{
if (!_Woo.HasValue)
{
var task = Task.Factory.StartNew<int>(model.GetWoo, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default());
task.ContinueWith(a => { _Woo = a.Result; }, CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.FromCurrentSynchronizationContext());
}
return _Woo;
}
set
{
_Woo = value;
NotifyPropertyChange("Woo");
}
}
请注意,使用Default TaskScheduler调度繁重的任务(意味着它将在从ThreadPool获取的线程上执行),同时使用当前同步上下文TaskScheduler调度延续(意味着它将在最初调用的任何线程上执行)获得)。
通常我会将2个TaskSchedulers(Background和Foreground / UI)注入ViewModel,以便我可以控制单元测试的调度策略,但我刚刚在上面的例子中引用了Default和FromCurrentSynchronizationContext。
(旁注:我不完全确定你的示例代码试图完成什么,如果有的话。我把它打开,以便它现在基本上是一个异步Get。用户界面将要求的价值Woo,它将使用Task触发异步刷新并立即返回其当前的NULL值。一旦GetWoo完成,它将在View Model上设置Woo的值,这将触发Property Changed事件(在正确的线程上) ),这将导致UI在屏幕上更新Woo的值。我认为它显示了无论如何都要从UI线程中解除升级的概念。)
答案 3 :(得分:1)
如果要更新UI,可以使用BackgroundWorker。像下面的例子。
var worker = new BackgroundWorker();
worker.DoWork += (o, ea) =>
{
Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new Action(
() =>
{
//you code for update UI
}));
};
worker.RunWorkerCompleted += (o, ea) =>
{
};
worker.RunWorkerAsync();