I am wondering why MVVM light is missing command with async execution? I believe there are many cases where this could be useful, so let me name one.
Let's say that our UI contains one container that contains multiple screens. User can close a particular screen or a container with multiple screens. Let's say that a user has issued a close command on the container. Container in return invokes close command on each screen, and it needs to wait for screen to be closed. This in practice can means validating data. saving, etc. For this reason we need to issue an async call to keep the UI from becoming unresponsive, and also we need to wait for task to complete, in order to continue.
So, if we have something like this in Command
public RelayCommand CloseCommand
{
get { return _closeCommand ?? _closeCommand = new RelayCommand( async () =>
{
foreach (var screen in Screens)
{
if (!await screen.CloseCommand.ExecuteAsync(null))
{
// do something
}
}
}) }
}
We could also expose additional method on screen, but in my opinion it should be task of RelayCommand, since it already exist there.
Or there is a different methodology to handle such scenario?
Probably because there are many different ways of doing it; I describe a few approaches in my MSDN article on the subject.
Asynchronous lifetime commands are especially tricky. Something like a "close" command must be carefully considered. Is there some indication that a close is in progress? What happens if the user closes more than once ("close" in particular can often be initiated by an OS or another app even if a "close button" is disabled)?
I found this being in some ways a solution to making async commands in MVVM Light.
If fact it overwrap a async method with Task.Run. Our wrapped method has to verify if it not executed twice, and catches errors from lower async executions.
private bool isLoading;
public bool IsLoading
{
get { return isLoading; }
set
{
if (value != isLoading)
{
Set(ref isLoading, value);
//Used to refresh Commands CanExecute laying on IsLoading
CommandManager.InvalidateRequerySuggested();
}
}
}
private RelayCommand loadCommand;
public RelayCommand LoadCommand
{
get
{
return loadCommand ?? (loadCommand = new RelayCommand(
() => Task.Run(LoadAsync),
() => !IsLoading
));
}
}
private async Task LoadAsync()
{
//Prevents double execution in case of many mouse clicks on button
if (IsLoading)
{
return;
}
//Assignments which need to be done on UI tread
DispatcherHelper.CheckBeginInvokeOnUI(() =>
{
IsLoading = true;
});
try
{
list = await service.LoadAsync();
...
}
catch (Exception e)
{
...
}
finally
{
DispatcherHelper.CheckBeginInvokeOnUI(() =>
{
IsLoading = false;
});
}
}
Related
I'm experiencing Reactive Programming (Rx) and one of its interesting feature is subscribing and observing on different threads. But here somehow it blocks the UI thread. Technically I don't have any method returning Task (async method), so here I'm trying to mimic a long process with Thread.Sleep:
IEnumerable<Item> _search(string searchText)
{
Thread.Sleep(3000);
//return result by querying ...
//...
return someResult;
}
I have a ViewModel class like this:
public class ViewModel {
public ViewModel(){
//this SubscribeOn may not be necessary but I just try it here for sure
SearchTextStream.SubscribeOn(NewThreadScheduler.Default)
.ObserveOn(DispatcherScheduler.Current)
.Subscribe(searchText => {
var items = _search(searchText);
}, ex => {
//handle error
});
}
public string SearchText
{
get
{
return _searchText.FirstAsync().Wait();
}
set
{
_searchText.OnNext(value);
}
}
ISubject<string> _searchText = new BehaviorSubject<string>("");
public IObservable<string> SearchTextStream
{
get
{
return _searchText.AsObservable().DistinctUntilChanged();
}
}
}
Actually without using Thread.Sleep, I can still see it blocks the UI but not very obvious, so I just use it to make it more obvious. As I said, the scenario here is that I have just a normal method without any task or async. It may be a long-running method. Using with RX, I don't know which should be done to make it behave like async (as when using a Task.Run)?
I'm testing on a WPF application if it matters.
You're calling _search(searchText) on the DispatcherScheduler.Current scheduler - and hence, with the Thread.Sleep you're blocking the UI.
You really should make _search return an observable.
IObservable<IEnumerable<Item>> _search(string searchText)
{
Thread.Sleep(3000);
//return result by querying ...
//...
return Observable.Return(new [] { new Item() });
}
Now the constructor should look like this:
public ViewModel()
{
SearchTextStream
.ObserveOn(System.Reactive.Concurrency.Scheduler.Default)
.SelectMany(searchText => _search(searchText))
.ObserveOnDispatcher()
.Subscribe(items =>
{
/* do something with `items` */
}, ex =>
{
//handle error
});
}
I am using MVVM and WPF, now I am calling async process from my viewmodel class's constructor like this:
Task.Run(() => this.MyMetho(someParam)).Wait();
The problem with this is that screen freeze it until the task ends.
Another way is to create a LoadDataMethod in the ViewModel and call it from the view in the event handler for UserControl_Loaded something like this
private async void UserControl_Loaded(object sender, RoutedEventArgs e)
{
VMRep vm = (VMRep)this.DataContext;
await vm.LoadDataMethod();
}
that way works better, but I guess there is a better way to do the load of async data for a View.
Thanks for your comments
You can create async factory method, and make your constructor private or protected
public static async bool Create() {
var control = new UserControl();
return await LoadDataMethod();
}
I guess there is a better way to do the load of async data for a View
The key is to realize that all ViewModels must initialize immediately. If you think about it, it doesn't make sense to initialize them asynchronously, because when WPF constructs your VM, it has to show it right away. Not 10 seconds from now whenever the download completes.
So, shift your thinking a bit, and the answer will become clearer. Your VM needs to initialize immediately, but you don't have the data to display yet. So the appropriate thing to do is to immediately initialize to a kind of "loading..." state and start the download of the data. When the data arrives, then you update the VM to show the data.
You can do it the way you have it:
private async void UserControl_Loaded(object sender, RoutedEventArgs e)
{
VMRep vm = (VMRep)this.DataContext;
await vm.LoadDataMethod();
}
but this doesn't provide a clean way for your View to detect whether the data is loading or has completed loading. I wrote a data-bindable Task<T> wrapper (updated version here) that helps with this kind of situation. It can be used like this:
public MyViewModel()
{
MyData = NotifyTask.Create(LoadDataAsync());
}
public NotifyTask<TMyData> MyData { get; }
and then your View can data-bind to MyData.Result to get to the data, as well as other properties such as MyData.IsNotCompleted to show/hide "loading" indicators.
Well, since I don't know exactly what you're trying to accomplish I'm trying my best to give you a brief explanation about Lazy
private Lazy<Task<string>> getInfo;
In this case I'm holding a field with Lazy<Task<string>> in your case it would be Lazy<Task<VMRep>>. I'm using this field, so that you can call this lazy initialzier inside your class.
public Laziness()
{
this.getInfo = new Lazy<Task<string>>(async () => await this.GetInfo());
}
In the constructor I'm assigning the value to the lazy field. In this case with the method GetInfo()
public string GetLazyInfo
{
get
{
return this.getInfo.Value.Result;
}
}
Exposing the getInfo field with a public property. And returning the result from the task inside the lazy.
private async Task<string> GetInfo()
{
await Task.Run(async () => await Task.Delay(5000));
return await Task.Run(() => "test");
}
And finally the method, where your magic can happen.
I use WPF 4.5 and MVVM Caliburn Micro and have following WPF code:
public class MainViewModel: Screen
{
public MainViewModel()
{
if (!ConnectServer())
{
Console.WriteLine("Connection failed");
return;
}
// Following method can only be run if server connection established
ProcessThis();
}
}
My code above has only one chance to connect and if it is failed it shows the view and do nothing. If I use while(!ConnectServer()) it will block the UI thread all the time, means nothing will be displayed to user while the connection is still failed.It is very ugly.
What I want:
if the connection is failed, means ConnectServer() returns false, it should wait for 10 seconds and try to connect again and again (eg. call a method RetryConnect()) till it is successful WITHOUT blocking the UI thread.
And after it is connected, it should continue to main thread and run ProcessThis().
Theoretically I know it needs background separated thread, but I don't know how to implement it simple and good. Please feel free to use my sample code to explain. Thank you in advance.
To start a background task you can use Task.Run method.
And to execute a code in the main thread you can use Dispatcher of the page (in case of VM context I have placed call of Application.Current.Dispatcher)
public class MainViewModel: Screen
{
public MainViewModel()
{
Task.Run(() =>
{
while (!ConnectServer())
{
Console.WriteLine("Connection failed");
Thread.Sleep(10*1000);
}
// Following method can only be run if server connection established
Application.Current.Dispatcher.Invoke(ProcessThis);
}
}
}
Instead of usage of Dispatcher you can utilize a new async/await functionality to implement it.
public class MainViewModel: Screen
{
public MainViewModel()
{
Initialize();
}
}
private async void Initialize()
{
await Task.Run(async () =>
{
while (!ConnectServer())
{
Console.WriteLine("Connection failed");
await Task.Delay(10*1000);
}
}
// Following method can only be run if server connection established
ProcessThis();
}
I am not quite sure, where my problem/mistake is.
I am using WPF in combination with the MVVM pattern and my problem is at the login.
My first attempt worked fine. I had several windows, each with their own ViewModel.
In the Login ViewModel I had following code running:
PanelMainMessage = "Verbindung zum Server wird aufgebaut";
PanelLoading = true;
_isValid = _isSupportUser = false;
string server = Environment.GetEnvironmentVariable("CidServer");
string domain = Environment.GetEnvironmentVariable("SMARTDomain");
try
{
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, server + "." + domain))
{
// validate the credentials
PanelMainMessage = "username und passwort werden überprüft";
_isValid = pc.ValidateCredentials(Username, _view.PasswortBox.Password);
PanelMainMessage = "gruppe wird überprüft";
_isSupportUser = isSupport(Username, pc);
}
}
catch (Exception ex)
{
//errormanagement -> later
}
if (_isValid)
{
PanelLoading = false;
if (_isSupportUser)
_mainwindowviewmodel.switchToQuestionView(true);
else
_mainwindowviewmodel.switchToQuestionView(false);
}
else
PanelMainMessage = "Verbindung zum Server konnte nicht hergestellt werden";
That part connects to an Active Directory and first checks if the login was succesfull and then, if the user has a certain ad group (in method isSupport)
I have a display in the view, which is like a progress bar. It is active when PanelLoading equals true.
Until now everything worked.
Then I created a main window with a contentcontrol in it and changed my views to user controls, so I could swap them. (The intention was, not to open/create a new window for every view).
When I execute the code now, my GUI blocks, until said part is executed. I have tried several ways...
Moving the code snippet into an additional method and starting it as an own thread:
Thread t1 = new Thread(() => loginThread());
t1.SetApartmentState(ApartmentState.STA);
t1.Start();
When I do it this way, I get an error that a ressource is owned by an another thread and thus cannot be accessed. (the calling thread cannot access this object because a different thread owns it)
Then, instead of an additional thread, trying to invoke the login part; login containing the previous code snippet
Application.Current.Dispatcher.Invoke((Action)(() =>
{
login();
}));
That does not work. At least not how I implemented it.
After that, I tried to run only the main part of the login snippet in a thread and after that finished, raising an previously registered event, which would handle the change of the content control. That is the part, where I get the error with the thread accessing a ressource owned by another thread, so I thought, I could work around that.
void HandleThreadDone(object sender, EventArgs e)
{
if (_isValid)
{
PanelLoading = false;
_mainwindowviewmodel.switchToQuestionView(_isSupportUser);
}
else
PanelMainMessage = "Verbindung zum Server konnte nicht hergestellt werden";
}
And in the login method I would call ThreadDone(this, EventArgs.Empty); after it finished. Well, I got the same error regarding the ressource owned by an another thread.
And now I am here, seeking for help...
I know that my code isn't the prettiest and I broke at least two times the idea behind the mvvm pattern. Also I have little understanding of the Invoke method, but I tried my best and searched for a while (2-3 hours) on stackoverflow and other sites, without succeeding.
To specify where the error with thread occurs:
_mainwindowviewmodel.switchToQuestionView(_isSupportUser);
which leads to the following method
public void switchToQuestionView(bool supportUser)
{
_view.ContentHolder.Content = new SwitchPanel(supportUser);
}
This is also one occasion, where I am not using Data Binding. I change the content of my contentcontrol:
<ContentControl Name="ContentHolder"/>
How would I implement this with Data Binding. Should the property have the type ContentControl? I couldn't really find an answer to this. And by changing this to DataBinding, would the error with the thread ownage be solved?
The project structure is as following:
Main View is entry point, in the constructor the data context is set to the mainviewmodel, which is created at that time. the main view has a contentcontrol, where I swap between my usercontrols, in this case my views.
from my mainviewmodel I set the content of the contentcontrol in the beginning at the usercontrol login, which creates a viewmodel in its contructors and sets it as datacontext.
The code snippets are from my loginviewmodel. Hope this helps.
I thought I found a workaround, but it still does not work. I forgot, how the timer works in the background, so it can be solved that way either.
The problem is that WPF, or XAML framawork in general, doesn't allow to modify visual elements on the main thread, from other threads. For solving this you should to distinguish which is the part of your code that update the view from the second thread. In your case I can see that:
_view.ContentHolder.Content = new SwitchPanel(supportUser);
changes the view.
For solving this you could try this answer. In which I use the synchronization context to the communication between threads.
Another way to solve it, (and it maybe is a wrong usage of the dispatcher) is using the dispatcher for "send" the actions that modify the view to the main thread. Some thing like this:
var dispatcher = Application.Current.Dispatcher;
//also could be a background worker
Thread t1 = new Thread(() =>
{
dispatcher .Invoke((Action)(() =>
{
login(); //or any action that update the view
}));
//loginThread();
});
t1.SetApartmentState(ApartmentState.STA);
t1.Start();
Hope this helps...
One common approach is to implement an AsyncRelayCommand (in some tutorials also named AsyncDelegateCommand and bind it to the WPF view.
Here's an example implementation I used for a demo project to get familiar with WPF, MVVM and DataBinding.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
public class AsyncRelayCommand : ICommand {
protected readonly Func<Task> _asyncExecute;
protected readonly Func<bool> _canExecute;
public event EventHandler CanExecuteChanged {
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public AsyncRelayCommand(Func<Task> execute)
: this(execute, null) {
}
public AsyncRelayCommand(Func<Task> asyncExecute, Func<bool> canExecute) {
_asyncExecute = asyncExecute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter) {
if(_canExecute == null) {
return true;
}
return _canExecute();
}
public async void Execute(object parameter) {
await ExecuteAsync(parameter);
}
protected virtual async Task ExecuteAsync(object parameter) {
await _asyncExecute();
}
}
Here's the LoginViewModel.
// ViewBaseModel is a basic implementation of ViewModel and INotifyPropertyChanged interface
// and which implements OnPropertyChanged method to notify the UI that a property changed
public class LoginViewModel : ViewModelBase<LoginViewModel> {
private IAuthService authService;
public LoginViewModel(IAuthService authService) {
// Inject authService or your Context, whatever you use with the IoC
// framework of your choice, i.e. Unity
this.authService = authService
}
private AsyncRelayCommand loginCommand;
public ICommand LoginCommand {
get {
return loginCommand ?? (loginCommand = new AsyncCommand(Login));
}
}
private string username;
public string Username {
get { return this.username; }
set {
if(username != value) {
username = value;
OnPropertyChanged("Username");
}
}
}
private string password;
public string Password {
get { return this.password; }
set {
if(password != value) {
password = value;
OnPropertyChanged("Password");
}
}
}
private async Task Search() {
return await Task.Run( () => {
// validate the credentials
PanelMainMessage = "username und passwort werden überprüft";
// for ViewModel properties you don't have to invoke/dispatch anything
// Only if you interact with i.e. Observable Collections, you have to
// run them on the main thread
_isValid = pc.ValidateCredentials(this.Username, this.Password);
PanelMainMessage = "gruppe wird überprüft";
_isSupportUser = isSupport(Username, pc);
}
} );
}
}
Now you bind Username and Password properties as Two-Way bindings to your text fields and Bind your LoginCommand command to your login button.
Last but not least, a very basic implementation of the ViewModelBase.
public abstract class ViewModelBase<T> : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName) {
var handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Some remarks at the end:
There are several issues with your code above, as you already mentioned. You reference the View from ViewModel. This pretty much breaks the whole thing and if you begin to reference views from ViewModel, you can skip MVVM wholly and use WPF's CodeBehind.
Also you should avoid referencing other ViewModels form your ViewModel, as this tightly couples them and makes unit-tests pretty hard.
To navigate between Views/ViewModels, one usually implement a NavigationService. You define the Interface of the NavigationService (i.e. INavigationService) in your model. But the implementation of the NavigationService happens in the Presentation Layer (i.e. the place/Project where your Views reside), since this is the only place where you can implement a NavigationService.
A navigation service is very specific to an application/platform and hence needs to be implemented for each platform a new (Desktop, WinRT, Silverlight). Same goes for the DialogService which displays Dialog messages/popups.
I'm trying reactiveui, and I don't understand how to make a simple scenario work: I have a method that listens for messages in a chat room. So, it's long running, and fires events when a message is found.
using reactiveui, I want to kick off this long running method when the window opens, and have new messages populated on the screen in a listbox. Because I'm using rx, I assumed I'd need an IObseravble version of the long running method, so I made one like this:
public static IObservable<Message> ObservableStream(int roomid, CancellationToken token)
{
return Observable.Create<Message>(
async (IObserver<Message> observer) =>
{
...
}
);
}
But, I've no idea how to plumb this into reactiveui. Would I need a ObservableAsPropertyHelper<List<Message>> ? At the moment I just kick off the long running method in a Task.Factory.Startnew and then on events I manually add to a list of messages, which is bound to the front end list box. This works but it's not using any reactiveui, and it strikes me that there should be a reactiveui way to do this:
public class MainWindowViewModel : ReactiveObject
{
private ThreadSafeObservableCollection<Message> _Messages;
public ThreadSafeObservableCollection<Message> Messages
{
get { return _Messages; }
set {
this.RaiseAndSetIfChanged(x => x._Messages, value);
}
}
public MainWindowViewModel()
{
Client.NewMessage += (sender, args) => Messages.Add(args.Message);
var task = Task.Factory.StartNew(() => Client.GetStream(token), token, TaskCreationOptions.LongRunning, TaskScheduler.Current);
}
}
// IN the code-behind
this.OneWayBind(ViewModel, x => x.Messages, x => x.MessageList.ItemsSource);
How about:
var Messages = ObservableStream(...).CreateCollection();
Then, you can listen to Messages for ItemAdded et al, or just bind it via OneWayBind and the UI will automatically update.
Would I need a ObservableAsPropertyHelper> ?
So, normally this would be a good idea for most Web API calls, but since you're streaming the list instead of replacing the list every time, you need to create a collection at startup and add items to it as they come in. Another way you could do this is:
var Messages = new ReactiveList<Message>();
ObservableStream(...).ObserveOn(RxApp.MainThreadScheduler).Subscribe(x => Message.Add(x));