wake-up-neo.com

C # async-Aufgaben zum Faulenzen zwingen?

Ich habe eine Situation, in der ich einen Objektbaum habe, der von einer speziellen Fabrik erstellt wurde. Dies ist etwas ähnlich einem DI-Container, aber nicht ganz.

Die Erstellung von Objekten erfolgt immer über den Konstruktor und die Objekte sind unveränderlich.

Einige Teile des Objektbaums werden in einer bestimmten Ausführung möglicherweise nicht benötigt und sollten nur langsam erstellt werden. Das Konstruktorargument sollte also nur eine Fabrik für die Erstellung von Anforderungen sein. Das sieht nach einem Job für Lazy aus.

Die Objekterstellung muss jedoch möglicherweise auf langsame Ressourcen zugreifen und ist daher immer asynchron. (Die Erstellungsfunktion der Objektfabrik gibt eine Task zurück.) Dies bedeutet, dass die Erstellungsfunktion für die Lazy async sein müsste und daher der eingefügte Typ Lazy<Task<Foo>> sein muss.

Aber ich hätte lieber nicht die doppelte Verpackung. Ich frage mich, ob es möglich ist, eine Task zu zwingen, faul zu sein, d. H. Eine Task zu erstellen, die garantiert nicht ausgeführt wird, bis sie erwartet wird. Wie ich es verstehe, kann ein Task.Run oder Task.Factory.StartNew zu einem beliebigen Zeitpunkt ausgeführt werden (z. B. wenn ein Thread aus dem Pool inaktiv ist), auch wenn nichts darauf wartet.

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;}
}
16
Sebastian Redl

Ich weiß nicht genau, warum Sie die Verwendung von Lazy<Task<>>, vermeiden möchten. Wenn Sie die API jedoch einfacher verwenden möchten, da dies eine Eigenschaft ist, können Sie dies mit einem unterstützenden Feld tun:

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

Auf diese Weise ist die Verwendung so, als wäre es nur eine Aufgabe, aber die Initialisierung ist faul und wird nur dann die Arbeit verursachen, wenn dies erforderlich ist.

19
Max

@ Max 'Antwort ist gut, aber ich möchte die Version hinzufügen, die auf Stephen Toub' Artikel basiert, der in Kommentaren erwähnt wird:

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 werden explizit von Lazy<Task<>> geerbt, es ist also klar, dass es Lazy und Asyncronous ist.

  2. Beim Aufruf des Basiskonstruktors wird lazyPartFactory in Task.Run umbrochen, um lange Blockierungen zu vermeiden, wenn diese Factory vor dem echten asynchronen Teil CPU-schwere Arbeit erfordert. Wenn es nicht Ihr Fall ist, ändern Sie es einfach in base(lazyPartFactory).

  3. Auf SlowPart kann über TaskAwaiter zugegriffen werden. Die öffentliche Schnittstelle von SomePart lautet:

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

Wenn Sie den Konstruktor für Task verwenden, wird die Task Lazy a.k.a erst ausgeführt, wenn Sie sagen, dass sie ausgeführt werden soll. Sie können also Folgendes tun:

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

Ergebnis:

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

Allerdings Ich empfehle Ihnen dringend, Rx.NET und IObservable zu verwenden, da Sie in Ihrem Fall weit weniger Probleme haben werden, wenn Sie weniger naive Fälle bearbeiten, um Ihre Aufgabe im richtigen Moment zu beginnen. Außerdem wird der Code meiner Meinung nach etwas sauberer

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

Ergebnis: 

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

Um es klarer zu sagen: Die Observable hier behandelt, wann die Task zu starten ist. Sie ist standardmäßig Lazy und führt die Task jedes Mal aus, wenn sie abonniert wird. (Hier wird der Abonnent verwendet, der die Verwendung des await-Schlüsselworts aktiviert).

Wenn Sie möchten, können Sie die Task nur einmal pro Minute (oder je) ausführen lassen und das Ergebnis für alle Abonnenten veröffentlichen, um beispielsweise Leistung zu sparen, wie in einer realen App. All dies und viele andere werden von erledigt Observables.

0
Uwy