-1

I have the following:

public class User 
{
        private readonly Lazy<Task<List<ReminderDb>>> _reminders;

        public SmsUserDb()
        {
            // Get _reminderReader from IoC

            _reminders = new Lazy<Task<List<ReminderDb>>>(async () => (List<ReminderDb>)await _reminderReader.Get(UserId));
        }

        public string UserId { get; set; }
        public Task<List<ReminderDb>> Reminders => _reminders.Value;
}

When I instantiate an object Like so:

    var n = new SmsUserDb {UserId = "123456"};
    var rems = await n.Reminders;

This code works and I see that n.Reminders ="Waiting for activation" until I hit await n.Reminders line.

However, when I query this User object from cache like so:

        var n1 = await _userReader.GetUserFromCache("+17084556675"); // return SmsUserDb
        var n2 = await n1.Reminders;

when it hits GetUserFromCache() it right away calls _reminderReader.Get(UserId) which calls cache again to get reminders. Then it simply times out. So Lazy doesn't work and most likely causes a deadlock.

public async Task<SmsUserDb> GetUserFromCache(string phoneNumber)
{
    var hash = CachedObjectType.smsuser.ToString();
    var fieldKey = string.Format($"{CachedObjectType.smsuser.ToString()}:user-{phoneNumber}");
    var result = await _cacheUserService.GetHashedAsync(hash, fieldKey);
    return result;
}

    private async Task<List<ReminderDb>> GetRemindersFromCache(string userId)
    {
        var hash = CachedObjectType.smsreminder.ToString();
        var fieldKey = string.Format($"{CachedObjectType.smsreminder.ToString()}:user-{userId}");
        var result = await _cacheService.GetHashedAsync(hash, fieldKey);
        return result ?? new List<ReminderDb>();
    }

What could be the problem?

ShaneKm
  • 20,823
  • 43
  • 167
  • 296
  • So what does `GetUserFromCache()` do? – Matthew Watson Aug 01 '18 at 08:05
  • simply gets user from cache (i've added that method). But my intention is that Reminders property would NOT be called until somewhere down the line I execute await user.Reminders; Also, If I remove Reminder property from SmsUserDb object It works fine. As if that Lazy was causing the problem. – ShaneKm Aug 01 '18 at 08:09
  • the question is not clear and the code incomplete. what exactly is this _reminderReader.Get(UserId) ? – Andrei Dragotoniu Aug 01 '18 at 08:17
  • _reminderReader.Get(userId) simply calls this => GetRemindersFromCache() – ShaneKm Aug 01 '18 at 08:19
  • This all works fine in my test code. The error must be something to do with the way you are caching your data. Is it serializing/deserializing, perhaps? You will need to post a complete compilable example, because all the code you've actually posted will work OK. – Matthew Watson Aug 01 '18 at 08:23

2 Answers2

1

This all works fine in my test code (shown below). Therefore the error must be in some code that you haven't shown us.

Here's some sample code that works - it proves that Lazy<T> is working correctly. Therefore the error is elsewhere in your code.

You must post a compilable repro in order for anyone to help you with this.

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Demo
{
    public class ReminderDb
    {
        public string Value;
    }

    public class ReminderReader
    {
        public async Task<List<ReminderDb>> Get(string userId)
        {
            Console.WriteLine("In Get() for " + userId);
            await Task.Delay(1000);
            Console.WriteLine("Returning from Get()");
            return new List<ReminderDb>{new ReminderDb{Value = userId}};
        }
    }

    public class Cache
    {
        public void Add(string key, SmsUserDb value)
        {
            _cache.Add(key, value);
        }

        public async Task<SmsUserDb> GetHashedAsync(string key)
        {
            await Task.Delay(1000);
            return _cache[key];
        }

        readonly Dictionary<string, SmsUserDb> _cache = new Dictionary<string, SmsUserDb>();
    }

    public class SmsUserDb
    {
        readonly Lazy<Task<List<ReminderDb>>> _reminders;
        readonly ReminderReader _reminderReader = new ReminderReader();

        public SmsUserDb()
        {
            _reminders = new Lazy<Task<List<ReminderDb>>>(async () => (List<ReminderDb>) await _reminderReader.Get(UserId));
        }

        public string                 UserId    { get; set; }
        public Task<List<ReminderDb>> Reminders => _reminders.Value;
    }

    static class Program
    {
        static async Task Main()
        {
            var db = new SmsUserDb(){UserId = "user ID"};
            var cache = new Cache();
            cache.Add("key", db);

            Console.WriteLine("Press RETURN to await cache.GetHashedAsync()");
            Console.ReadLine();
            var result = await cache.GetHashedAsync("key");

            Console.WriteLine("Press RETURN to await Reminders[0].Value");
            Console.ReadLine();
            Console.WriteLine((await result.Reminders)[0].Value);
        }
    }
}
Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
0

Found the problem. I was using the same class to write to cache, that is:

public class User 
{
        private readonly Lazy<Task<List<ReminderDb>>> _reminders;

        public SmsUserDb()
        {
            // Get _reminderReader from IoC

            _reminders = new Lazy<Task<List<ReminderDb>>>(async () => (List<ReminderDb>)await _reminderReader.Get(UserId));
        }

        public string UserId { get; set; }
        public Task<List<ReminderDb>> Reminders => _reminders.Value;
}

After using a different class for writing (below) and reading (above), all works as desired. thanks

public class CacheUser {
    public string UserId { get; set; }
    public List<ReminderDb> Reminders {get; set; }
}
ShaneKm
  • 20,823
  • 43
  • 167
  • 296