remarks cref c# .net asynchronous .net-core

cref - ¿Las tareas asincrónicas de Force C#son flojas?



remarks c# (3)

Al usar el constructor para Task la tarea lazy aka no se ejecuta hasta que la diga que se ejecute, por lo que podría hacer algo como esto:

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

Resultado:

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

Sin embargo , le recomiendo que use Rx.NET e IObservable ya que en su caso obtendrá menos problemas para manejar casos menos ingenuos para comenzar su tarea en el momento correcto. También hace que el código sea un poco más limpio en mi opinión

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

Resultado:

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

Para ser más claro: El Observable aquí maneja cuando comenzar la tarea, es Lazy por defecto y ejecutará la tarea cada vez que se suscriba (aquí el suscriptor es usado por el awaiter que permite el uso de la palabra clave await).

Si es necesario, puede hacer que la tarea se ejecute solo una vez por minuto (o nunca) y publicando su resultado en todos los suscriptores para ahorrar rendimiento, por ejemplo, como en una aplicación del mundo real, todo esto y muchos más son gestionados por observables.

Tengo una situación en la que tengo un árbol de objetos creado por una fábrica especial. Esto es algo similar a un contenedor DI, pero no del todo.

La creación de objetos siempre ocurre a través del constructor, y los objetos son inmutables.

Algunas partes del árbol de objetos pueden no ser necesarias en una ejecución dada y deben crearse de forma perezosa. Por lo tanto, el argumento del constructor debe ser algo que solo sea una fábrica para la creación bajo demanda. Esto parece un trabajo para Lazy .

Sin embargo, la creación de objetos puede necesitar acceder a recursos lentos y, por lo tanto, siempre es asíncrona. (La función de creación de la fábrica de objetos devuelve una Task ). Esto significa que la función de creación para Lazy debería ser asincrónica, y por lo tanto, el tipo inyectado debe ser Lazy<Task<Foo>> .

Pero prefiero no tener el doble envoltorio. Me pregunto si es posible obligar a una Task a ser floja, es decir, a crear una Task que garantice que no se ejecutará hasta que se espere. Tal como lo entiendo, una Task.Run o Task.Factory.StartNew puede comenzar a ejecutarse en cualquier momento (por ejemplo, si un hilo del pool está inactivo), incluso si no hay nada esperando por él.

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


La respuesta de @Max es buena, pero me gustaría agregar la versión que está construida sobre el artículo de Stephen Toub mencionado en los comentarios:

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 se ha heredado explícitamente de Lazy<Task<>> por lo que está claro que es vago y asíncrono .

  2. El constructor de la base de llamadas ajusta lazyPartFactory a Task.Run para evitar el bloqueo prolongado si esa fábrica necesita algo de trabajo pesado antes de la parte asincrónica real. Si no es tu caso, simplemente base(lazyPartFactory) a base(lazyPartFactory)

  3. Se puede acceder a SlowPart a través de TaskAwaiter. Entonces la interfaz pública de SomePart es:

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

No estoy seguro de por qué quieres evitar el uso de Lazy<Task<>>, pero si solo es para mantener la API más fácil de usar, ya que es una propiedad, puedes hacerlo con un campo de respaldo:

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

De esta forma, el uso es como si fuera solo una tarea, pero la inicialización es floja y solo incurrirá en el trabajo si es necesario.