HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Using list of tasks to obtain and cache data

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
obtaintaskscacheusingandlistdata

Problem

I develop Windows Phone App with Prism framework (MVVM).

I use data caching. To get data I use proxy service. Proxy service creates two tasks:

  • the first task is receiving data from a web service and update data in local database (SqLite).



  • the second task is receiving data from the local database.



The ViewModel launches tasks and displays data on UI.

It works, but I think it can be done much better. Any ideas?

ViewModel

public class UserProfilePageViewModel : ViewModel, IUserProfilePageViewModel
{

    private readonly IUserServiceProxy _userServiceProxy;

    private IUserItemViewModel _userProfile;
    public IUserItemViewModel UserProfile
    {
        get { return _userProfile; }
        set { SetProperty(ref _userProfile, value); }
    }

    public UserProfilePageViewModel(IUserServiceProxy userServiceProxy)
    {
        if (userServiceProxy == null) { throw new ArgumentNullException("The userServiceProxy can't be null"); }          
        _userServiceProxy = userServiceProxy;       
    }

    public async override void OnNavigatedTo(object navigationParameter, Windows.UI.Xaml.Navigation.NavigationMode navigationMode, Dictionary viewModelState)
    {           
        _userId = navigationParameter as string;
        await Load(navigationParameter as string);
    }       

    public async Task Load(string id)
    {          

        var taskList = _userServiceProxy.GetUserTaskList(id);

        while (taskList.Count > 0)
        {                
            var t = await Task.WhenAny(taskList);
            taskList.Remove(t);
            try
            {
                var result = await t;
                //update UI 
                UserProfile.Model = result.Result;
            }
            catch (OperationCanceledException) { }
            catch (Exception) { }
        }
        await Task.WhenAll(taskList);

    }

}


ServiceProxy

```
public class UserServiceProxy : IUserServiceProxy
{
private readonly IUserA

Solution

Let's start with the ServiceProxy.

  • You're misusing ArgumentNullException, if you pass only one parameter it should contain the name of the parameter which value is null.



  • You don't need Task.Factory.StartNew at all, as you just kill all the benefits of task-oriented approach with it. All you need to do is just capture the task, without awaiting on it. As a result you don't need all those .Unwrap() calls as well.



  • Since you're just outputting the collection of different tasks that eventually return the ResponseWrapper object - I would prefer to return the IEnumerable instead of List.



  • And finally - split different approaches of getting the result record into different methods (if not classes) so that the code remains clean. If you use an IoC container you may even want to split the implementation into different classes implementing the same interface, but that would be too much for this example.



As a result we should get something like this:

UserServiceProxy

public class UserServiceProxy : IUserServiceProxy
{
    private readonly IUserApi _userApi;
    private readonly IUserRepository _userRepository;
    private readonly IUserSettings _userSettings;
    private readonly IAuthService _authService;

    public UserServiceProxy(IUserApi userApi, IUserRepository userRepository, IUserSettings userSettings, IAuthService authService)
    {
        if (userApi == null) { throw new ArgumentNullException("userApi", "The userApi can't be null"); }
        if (userRepository == null) { throw new ArgumentNullException("userRepository", "The userRepository can't be null"); }
        if (userSettings == null) { throw new ArgumentNullException("userSettings", "The settings can't be null"); }
        if (authService == null) { throw new ArgumentNullException("authService", "The authService can't be null"); }
        _userApi = userApi;
        _userRepository = userRepository;
        _userSettings = userSettings;
        _authService = authService;
    }

    public IEnumerable>> GetUserTasks(string id, CancellationToken cancellationToken = new CancellationToken())
    {
        yield return GetUserFromWebAsync(id, cancellationToken);
        yield return GetUserFromLocalAsync(id);
    }

    private async Task> GetUserFromLocalAsync(string id)
    {
        var localUser = await _userRepository.LoadByUserIdAsync(id) ?? new LocalSQLite.Models.User();
        return Models.WebApi.ResponseWrapper.Wrap(localUser.ToPoco());
    }

    private async Task> GetUserFromWebAsync(string id, CancellationToken cancellationToken)
    {
        var poco = await _userApi.GetAsync(id, cancellationToken);
        await _userRepository.MergeUser(LocalSQLite.Models.User.FromPoco(poco.Result));
        return poco;
    }
}


Now let's look at the Load method. The main issue there is the processing of each task as it finishes. It can easily be done like this (note that both methods are no longer async, as they do not await on tasks):

private void UpdateUi(ResponseWrapper user)
    {
        //update UI 
        UserProfile.Model = result.Result;
    }

    public Task Load(string id)
    {
        var uiUpdateTasks = _userServiceProxy.GetUserTasks(id)
            .Select(async userTask => UpdateUi(await userTask));

        return Task.WhenAll(uiUpdateTasks);
    }

Code Snippets

public class UserServiceProxy : IUserServiceProxy
{
    private readonly IUserApi _userApi;
    private readonly IUserRepository _userRepository;
    private readonly IUserSettings _userSettings;
    private readonly IAuthService _authService;

    public UserServiceProxy(IUserApi userApi, IUserRepository userRepository, IUserSettings userSettings, IAuthService authService)
    {
        if (userApi == null) { throw new ArgumentNullException("userApi", "The userApi can't be null"); }
        if (userRepository == null) { throw new ArgumentNullException("userRepository", "The userRepository can't be null"); }
        if (userSettings == null) { throw new ArgumentNullException("userSettings", "The settings can't be null"); }
        if (authService == null) { throw new ArgumentNullException("authService", "The authService can't be null"); }
        _userApi = userApi;
        _userRepository = userRepository;
        _userSettings = userSettings;
        _authService = authService;
    }

    public IEnumerable<Task<ResponseWrapper<User>>> GetUserTasks(string id, CancellationToken cancellationToken = new CancellationToken())
    {
        yield return GetUserFromWebAsync(id, cancellationToken);
        yield return GetUserFromLocalAsync(id);
    }

    private async Task<ResponseWrapper<User>> GetUserFromLocalAsync(string id)
    {
        var localUser = await _userRepository.LoadByUserIdAsync(id) ?? new LocalSQLite.Models.User();
        return Models.WebApi.ResponseWrapper<Models.User.User>.Wrap(localUser.ToPoco());
    }

    private async Task<ResponseWrapper<User>> GetUserFromWebAsync(string id, CancellationToken cancellationToken)
    {
        var poco = await _userApi.GetAsync(id, cancellationToken);
        await _userRepository.MergeUser(LocalSQLite.Models.User.FromPoco(poco.Result));
        return poco;
    }
}
private void UpdateUi(ResponseWrapper<User> user)
    {
        //update UI 
        UserProfile.Model = result.Result;
    }

    public Task Load(string id)
    {
        var uiUpdateTasks = _userServiceProxy.GetUserTasks(id)
            .Select(async userTask => UpdateUi(await userTask));

        return Task.WhenAll(uiUpdateTasks);
    }

Context

StackExchange Code Review Q#79188, answer score: 3

Revisions (0)

No revisions yet.