强制C#异步任务是懒惰的吗?

时间:2017-07-24 08:44:04

标签: c# .net asynchronous .net-core

我的情况是我有一个特殊工厂创建的对象树。这有点类似于DI容器,但不完全相同。

对象的创建总是通过构造函数发生,并且对象是不可变的。

在给定的执行中可能不需要对象树的某些部分,应该懒惰地创建。因此构造函数参数应该只是按需创建的工厂。这似乎是Lazy的工作。

但是,对象创建可能需要访问慢资源,因此始终是异步的。 (对象工厂的创建函数返回Task。)这意味着Lazy的创建函数需要是异步的,因此注入的类型需要为{{1} }。

但我宁愿没有双层包装。我想知道是否有可能迫使Lazy<Task<Foo>>变得懒惰,即创建一个保证在等待之前不会执行的Task。据我了解,TaskTask.Run可能随时开始执行(例如,如果来自池的线程空闲),即使没有任何东西在等待它。

Task.Factory.StartNew

4 个答案:

答案 0 :(得分:20)

我不确定您为什么要避免使用Lazy<Task<>>,,但如果只是为了让API更易于使用,因为这是一个属性,您可以使用支持字段来执行此操作:

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;
}

这样,用法就好像它只是一个任务,但初始化是懒惰的,只有在需要时才会产生。

答案 1 :(得分:2)

@最大&#39;答案很好,但我想添加一个建立在Stephen Toub&#39;之上的版本。评论中提到的文章:

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明确继承自Lazy<Task<>>,因此很清楚它是 lazy asyncronous

  2. 调用基础构造函数将lazyPartFactory包装到Task.Run以避免长块,如果该工厂在真正的异步部分之前需要一些cpu繁重的工作。如果不是您的情况,只需将其更改为base(lazyPartFactory)

  3. 即可
  4. 可以通过TaskAwaiter访问SlowPart。所以SomePart&#39;公共接口是:

    • var eagerValue = somePart.EagerPart;
    • var slowValue = await somePart;

答案 2 :(得分:0)

使用Task的构造函数使任务变得懒惰a.k.a在你说它运行之前没有运行,所以你可以这样做:

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;
} 

结果:

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

然而我强烈建议您使用Rx.NETIObservable,因为在您的情况下,您可以减少处理不太天真的案件以便在右侧开始任务的麻烦时刻。 此外,它使我的代码中的代码更清晰

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;
}

结果:

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

更清楚:Observable这里处理何时启动任务,默认情况下它是Lazy并且每次订阅时都会运行任务(这里订阅由awaiter使用,允许使用await关键字)。

如果需要,您可以每隔一分钟(或任何时间)运行一次任务,并在所有订阅者中发布结果以保存性能,例如在真实世界的应用程序中,所有这些以及更多由观察者处理。

答案 3 :(得分:-1)

声明:

private Lazy<Task<ServerResult>> _lazyServerResult;`


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

用法:

ServerResult result = await __lazyServerResult.Value;