Deadlock on Async lazy with PublicationOnly

Viewed 398

Assume the following code

public class ValuesController : ApiController
{
    // GET api/values
    public IEnumerable<string> Get()
    {
        Lazy<TimeSpan> lm = new Lazy<TimeSpan>(GetDataAsync1, System.Threading.LazyThreadSafetyMode.PublicationOnly);

        return new string[] { "value1", "value2", lm.Value.ToString() };
    }

    private TimeSpan GetDataAsync1()
    {

        return GetTS().ConfigureAwait(false).GetAwaiter().GetResult();

    }

    // I Cant change this method, and what is inside it...
    private async Task<TimeSpan> GetTS()
    {
        var sw = Stopwatch.StartNew();

        using (var client = new HttpClient())
        {
            var result = await client.GetAsync("https://www.google.com/");
        }

        sw.Stop();
        return sw.Elapsed;
    }
}

The point is that I am getting some data from remote server, and want to cache that for later use. As remote server may fail in a given point, I dont want to cache exception, but only success result... So keeping than awaiting the value will not work for me

// Cant use this, because this caches failed exception as well
Lazy<Task...> lz = ...
await lz.Value

But above snipped, as expected produce a deadlock, given that I cant change GetTS, is it possible to force Lazy work with my logic?

2 Answers

The problem actually has nothing to do with Lazy<T>. The deadlock is because it's blocking on asynchronous code.

In this code:

private TimeSpan GetDataAsync1()
{
  return GetTS().ConfigureAwait(false).GetAwaiter().GetResult();
}

the ConfigureAwait(false) does nothing. ConfigureAwait configures awaits, not tasks, and there is no await there.

The best option is to go async all the way. If exceptions are a concern, you can use AsyncLazy<T> and pass AsyncLazyFlags.RetryOnFailure.

If you can't go async all the way, the next best option would be to go synchronous all the way. If you can't do either of these, then you'll have to choose a sync-over-async hack; be aware that there is no hack that works in all situations.

Actually I found no easy way of doing this, but the feature request discussion in .net github gives a complete picture of current situation

GitHub: Add async support to System.Lazy

Actually 5th point from last @StephenCleary answers my question for exception caching

  1. Resetting on exceptions is also an important use case. I've addressed this by adding another async lazy flag that will reset the AsyncLazy to an uninitialized state if an exception is thrown from the delegate. All existing accessors see the exception, but the next accessor will retry the delegate.
Related