22

I have a situation where I have an object tree created by a special factory. This is somewhat similar to a DI container, but not quite.

Creation of objects always happens via constructor, and the objects are immutable.

Some parts of the object tree may not be needed in a given execution and should be created lazily. So the constructor argument should be something that is just a factory for on-demand creation. This looks like a job for Lazy.

However, object creation may need to access slow resources and is thus always async. (The object factory's creation function returns a Task.) This means that the creation function for the Lazy would need to be async, and thus the injected type needs to be Lazy<Task<Foo>>.

But I'd rather not have the double wrapping. I wonder if it is possible to force a Task to be lazy, i.e. to create a Task that is guaranteed to not execute until it is awaited. As I understand it, a Task.Run or Task.Factory.StartNew may start executing at any time (e.g. if a thread from the pool is idle), even if nothing is waiting for it.

public class SomePart
{
  // Factory should create OtherPart immediately, but SlowPart
  // creation should not run until and unless someone actually
  // awaits the task.
  public SomePart(OtherPart eagerPart, Task<SlowPart> lazyPart)
  {
    EagerPart = eagerPart;
    LazyPart = lazyPart;
  }

  public OtherPart EagerPart {get;}
  public Task<SlowPart> LazyPart {get;}
}
Sebastian Redl
  • 69,373
  • 8
  • 123
  • 157
  • 3
    This helpful? https://blogs.msdn.microsoft.com/pfxteam/2011/01/15/asynclazyt/ – DavidG Jul 24 '17 at 08:50
  • @DavidG Thanks, I've read this article before. This would be the workaround I would use if I can't make a plain `Task` lazy, but I'm still hoping for a way to do that. – Sebastian Redl Jul 24 '17 at 08:56
  • 3
    Await will not start a task for you if it's not already started, so you cannot "create a Task that is guaranteed to not execute until it is awaited" (unless you start it yourself somewhen before await happens). – Evk Jul 24 '17 at 09:01
  • I don't mind `Lazy>` myself, but I do have a convenience [`AsyncLazy` type](https://github.com/StephenClearyArchive/AsyncEx.Coordination/blob/master/src/Nito.AsyncEx.Coordination/AsyncLazy.cs) in my [AsyncEx library](https://www.nuget.org/packages/Nito.AsyncEx/). – Stephen Cleary Jul 24 '17 at 14:15
  • Related: [Enforce an async method to be called once](https://stackoverflow.com/questions/28340177/enforce-an-async-method-to-be-called-once/) – Theodor Zoulias Jun 07 '21 at 10:58

4 Answers4

29

I'm not sure exactly why you want to avoid using Lazy<Task<>>,, but if it's just for keeping the API easier to use, as this is a property, you could do it with a backing field:

public class SomePart
{
    private readonly Lazy<Task<SlowPart>> _lazyPart;

    public SomePart(OtherPart eagerPart, Func<Task<SlowPart>> lazyPartFactory)
    {
        _lazyPart = new Lazy<Task<SlowPart>>(lazyPartFactory);
        EagerPart = eagerPart;
    }

    OtherPart EagerPart { get; }
    Task<SlowPart> LazyPart => _lazyPart.Value;
}

That way, the usage is as if it were just a task, but the initialisation is lazy and will only incur the work if needed.

Max
  • 721
  • 5
  • 8
7

@Max' answer is good but I'd like to add the version which is built on top of Stephen Toub' article mentioned in comments:

public class SomePart: Lazy<Task<SlowPart>>
{
    public SomePart(OtherPart eagerPart, Func<Task<SlowPart>> lazyPartFactory)
        : base(() => Task.Run(lazyPartFactory))
    {
        EagerPart = eagerPart;
    }

    public OtherPart EagerPart { get; }
    public TaskAwaiter<SlowPart> GetAwaiter() => Value.GetAwaiter();
}
  1. SomePart's explicitly inherited from Lazy<Task<>> so it's clear that it's lazy and asyncronous.

  2. Calling base constructor wraps lazyPartFactory to Task.Run to avoid long block if that factory needs some cpu-heavy work before real async part. If it's not your case, just change it to base(lazyPartFactory)

  3. SlowPart is accessible through TaskAwaiter. So SomePart' public interface is:

    • var eagerValue = somePart.EagerPart;
    • var slowValue = await somePart;
pkuderov
  • 3,501
  • 2
  • 28
  • 46
0

Declaration:

private Lazy<Task<ServerResult>> _lazyServerResult;`


ctor()
{
    _lazyServerResult = new Lazy<Task<ServerResult>>(async () => await 
        GetServerResultAsync())
}

Usage:

ServerResult result = await _lazyServerResult.Value;
-1

Using the constructor for Task make the task lazy a.k.a not running until you say it to run, so you could do something like this:

public class TestLazyTask
{
    private Task<int> lazyPart;

    public TestLazyTask(Task<int> lazyPart)
    {
        this.lazyPart = lazyPart;
    }

    public Task<int> LazyPart
    {
        get
        {
            // You have to start it manually at some point, this is the naive way to do it
            this.lazyPart.Start();
            return this.lazyPart;
        }
    }
}


public static async void Test()
{
    Trace.TraceInformation("Creating task");
    var lazyTask = new Task<int>(() =>
    {
        Trace.TraceInformation("Task run");
        return 0;
    });
    var taskWrapper = new TestLazyTask(lazyTask);
    Trace.TraceInformation("Calling await on task");
    await taskWrapper.LazyPart;
} 

Result:

SandBox.exe Information: 0 : Creating task
SandBox.exe Information: 0 : Calling await on task
SandBox.exe Information: 0 : Task run

However I strongly recommend you to use Rx.NET and IObservable as in your case you will get way less troubles for handling less naive cases to start your task at the right moment. Also it makes the code a bit cleaner in my opinion

public class TestLazyObservable
{
    public TestLazyObservable(IObservable<int> lazyPart)
    {
        this.LazyPart = lazyPart;
    }

    public IObservable<int> LazyPart { get; }
}


public static async void TestObservable()
{
    Trace.TraceInformation("Creating observable");
    // From async to demonstrate the Task compatibility of observables
    var lazyTask = Observable.FromAsync(() => Task.Run(() =>
    {
        Trace.TraceInformation("Observable run");
        return 0;
    }));

    var taskWrapper = new TestLazyObservable(lazyTask);
    Trace.TraceInformation("Calling await on observable");
    await taskWrapper.LazyPart;
}

Result:

SandBox.exe Information: 0 : Creating observable
SandBox.exe Information: 0 : Calling await on observable
SandBox.exe Information: 0 : Observable run

To be more clear: The Observable here handle when to start the task, it is Lazy by default and will run the task everytime it is subscribed (here subscribe is used by the awaiter that enable the use of the await keyword).

You could, if you need to, make the task run only once every minute (or ever) and having its result published across all subscribers to save performance for instance, like in a real world app, all of this and many more is handled by observables.

Uwy
  • 457
  • 7
  • 13
  • 2
    `I strongly recommend you to use Rx.NET and IObservable` - that's what I call an overkill for this particular problem. Reactiveness is cool but I'd keep it for another time – pkuderov Jul 24 '17 at 09:35
  • It is not overkill if you don't overlook all the amount of particular problems you have to handle on daily basis in a real world app. You can argue this is like the good old "use jquery" thing, and it is somehow, but I really felt like OP scenario was relevant to this. Plus I did answer his question without Observables, I mentioned it as something he should use. – Uwy Jul 24 '17 at 09:42
  • 1
    that's why it's better to not create even more problems and raise entropy by chosing improper tools, imho – pkuderov Jul 24 '17 at 09:47
  • It is the most proper tool to do lazy async processing I can think of. It do not require special boilerplate code and is adaptive to the use case. I think, and it is my opinion, that in this case it will save more troubles than it creates compared to solving the issue with a quick workaround – Uwy Jul 24 '17 at 09:54
  • 1
    Ok, maybe I just don't understand Rx quite right but I thought it is mostrly to deal with push-based asyncronous streams of events/objects/etc. – pkuderov Jul 24 '17 at 10:13