We received a great question via email today and I wanted to share it (and the answer!) with you:
In the previous version of Devforce and MVVM, the ViewModel
class called the repository something like this
private
void
FetchMembers() { this.IsBusy
= true; Repository.GetMembers( (members) => //
query success { this.IsBusy
= false; ResetMembersList(members); }, (error) => //
failed { this.IsBusy
= false; DisplayMessage(error.Message); }); } private
void
ResetMembersList(IEnumerable<Member>
members) { Members.Clear(); members.ForEach(Members.Add); }
Now using Async and Devforce 2012, my GetMembersAsync returns
a Task<IEnumerable<Member>>. It doesn’t seem right that the
ViewModel receives a Task and has to decipher the result and pull it apart to
get the result. It would seem more logical to have a Task service. In my Task
service I have a GetMembers method that calls the GetMembersAsync method of the
Repository. The ViewModel calls this method now and no longer calls the
Repository. The GetMembers method gets back a Task. It is responsible for
determining the outline of the call, unbundling the result and send back an
IEnumerable collection back to the ViewModel. Now the ViewModel does not have to
know about Tasks. It puts a layer between the ViewModel and Repository.
What do you think about this idea? Is there a better way to
do this? |
Yes, what you are describing as a service is all
handled by the compiler. Rich compiler support for the Task-based Asynchronous Pattern does it all for you. You rarely touch the actual Task. So, let’s
look at how this would look with the example below. As you said GetMemberAsync
returns Task<IEnumerable<Member>>, so the FetchMembers method done
properly looks like this. The async keyword tells the compiler that it needs to
apply the magic sauce.
private
async
void
FetchMembers()
{
try
{
this.IsBusy
= true;
ResetMembersList(await
Repository.GetMembersAsync());
}
catch
(Exception
error)
{
DisplayMessage(error.Message);
}
finally
{
this.IsBusy
= false;
}
}
private
void
ResetMembersList(IEnumerable<Member>
members)
{
Members.Clear();
members.ForEach(Members.Add);
}
Notice, you never need to
“decipher” the Task. If GetMemberAsync succeeds, the await keyword extracts the
result and passes it to ResetMembersList. If GetMemberAsync fails, await throws
an exception, which you can simply handle with the try catch and you can take care
of the busy state in the finally. One of the great things about the Task-based
Asynchronous Pattern is that you write the code as if it was synchronous, except
you put await in front of asynchronous methods.
private
async
Task
FetchMembers()
{
try
{
this.IsBusy
= true;
ResetMembersList(await
Repository.GetMembersAsync());
}
catch
(Exception
error)
{
DisplayMessage(error.Message);
}
finally
{
this.IsBusy
= false;
}
}
private
void
ResetMembersList(IEnumerable<Member>
members)
{
Members.Clear();
members.ForEach(Members.Add);
}
If you also want to return a
result value, you can do that to. Let’s say you want FetchMembers to return the list
of members. In that case it would look like this.
private
async
Task<IEnumerable<Member>>
FetchMembers()
{
try
{
this.IsBusy
= true;
var
members = await
Repository.GetMembersAsync();
ResetMembersList(members);
return
members;
}
catch
(Exception
error)
{
DisplayMessage(error.Message);
throw
error; //
Fail Task
}
finally
{
this.IsBusy
= false;
}
}
private
void
ResetMembersList(IEnumerable<Member>
members)
{
Members.Clear();
members.ForEach(Members.Add);
}
Again, notice you don’t actually
return Task<IEnumberable<Member>>, you just return
IEnumerable<Member> and the compiler packages that up in the returned
Task. In case of an error, just re-throw the exception and the compiler will
then fault the Task, so that the caller knows not to try to extract the result
value.