The question may sound a bit abstract, here's an example of a non async piece of code that does some lazy loading and ensures that the lazy loading is only done once by the first thread:
public class LazyExample
{
    private object ExpensiveResource = null;
    private object ExpensiveResourceSyncRoot = new object();
    public object GetExpensiveResource()
    {
        if (ExpensiveResource != null) // Checkpoint 1
            return ExpensiveResource;
        lock (ExpensiveResourceSyncRoot) // initially there will be a queue of threads waiting here that have already passed Checkpoint 1
        {
            if (ExpensiveResource != null) // prevent re-retrieval by all subsequent threads that passed Checkpoint 1
                return ExpensiveResource;
            // create the expensive resource but do not assign yet...
            object expensiveResource = new object();
            // initialize the expensive resource, for example:
            // - Call an API...
            // - Do some Database lookups...
            Thread.Sleep(1); 
            // finally as last step before releasing the lock, assign the fully initialized expensive object
            ExpensiveResource = expensiveResource;
        }
        return ExpensiveResource;
    }
}
In our lib, the async virus has started to infest many calls. Since awaiting is not allowed directly inside a lock we now wrap the async stuff in a new method like so:
public class LazyExample
{
    private object ExpensiveResource = null;
    private object ExpensiveResourceSyncRoot = new object();
    public async Task<object> GetExpensiveResourceAsync()
    {
        if (ExpensiveResource != null) // Checkpoint 1
            return ExpensiveResource;
        lock (ExpensiveResourceSyncRoot) // initially there will be a queue of threads waiting here that have already passed Checkpoint 1
        {
            if (ExpensiveResource != null) // prevent re-retrieval by all subsequent threads that passed Checkpoint 1
                return ExpensiveResource;
            
            // assign the fully initialized expensive object
            ExpensiveResource = CreateAndInitializeExpensiveResourceAsync().Result;
        }
        return ExpensiveResource;
    }
    private async Task<object> CreateAndInitializeExpensiveResourceAsync()
    {
        object expensiveResource = new object();
        // initialize the expensive resource, this time async:
        await Task.Delay(1);
        return expensiveResource;
    }
}
This however feels like putting a zip-tie around a safety latch and defeating the cause.
In seemingly random cases we need to wrap a call in order to prevent deadlocks like so:
ExpensiveResource = Task.Run(CreateAndInitializeExpensiveResourceAsync).Result;
This will force the method to run in a different thread and cause the current thread to go idle until it joins (extra overhead for no good reason as far as I can tell).
So my question is: is it safe to offload async stuff to a separate method (a new stack frame if you will) inside a lock or are we indeed defeating a safety latch?
 
    