Co vy na taketo pouzitie yield?

Scenar je nejaka dlho trvajuca uloha, ktora reportuje progress a da sa stornovat.

Zvycajne by som to riesil cez Event na reportovanie progresu a TaskCancellationToken.

Ako by ste sa zatvarili, keby ste uvideli takyto kod:

public ProgressStatus BtnClickProgress {get; set;}

async void BtnClick() 
{
     await foreach(var progress in LongRunningTaskAsync())
     {
         BtnClickProgress  = prograss;
         if (progress.HasErrors) 
         {
               break;
         }
    }
}

IAsyncEnumerable<ProgressStatus> LongRunningTaskAsync() 
{
    var errors = new List<string>();
    yield return new ProgressStatus(0, "Started");
    await Task.Delay(1000);

    for(int i = 10; i <= 100, i+= 10) {
       errors.Add("I have found some error, you can cancel me");
       Task.Delay(100);
       yield return new ProgressStatus(i, "working", errors.ToList());
    }
    yield return new ProgressStatus(100, "done", errors.ToList());
}

Verzia moze byt kludne aj s IEnumerable aj IAsyncEnumerable, podla situacie.

Vyhoda je, ze tam niesu ziadne delegaty (netreba sa bat memory leaku) a tiez netreba riesit TaskCanceledException, vytvaray CancelationToken premennu, cize je to cistejsi kod.

Nevyhoda, samozrejme je, ze to je nezvycajne a neviem kolko ludi tomu rozumie.

Pomohlo by prihodit triedu ProgressStatus. Ale neviem, či sa mi to páči. Po prvé existenciou CancellationTokenu vieš detegovať, že to už je spustené. Po druhé nevidím tam mechanizmus, ktorým by si to vedel ten task zrušiť.

  1. CancelationToken sa nevyhnes, budes ho musiet stracit do nejakych dlho trvajuciych asynkovych operacii.
  2. CancelationToken by som ocakaval, kedze ide o async operaciu.
  3. Co sa tyka nezvycajnosti, tak ano nie je to ocakavane spravanie, ale podobny patern sa pouziva pri gRPC aj SignalR streamovani sprav. Tiez som nieco podobne videl v Javascripte, ale tam clovek nikdy nevie co je dobre a co nie.

Co takto event nahradit hlupim Action<ProgressStatus>, vyhol by si sa problemom s memory leakmi ?

Neriesi podobne problemy nahodou Reactive extensions? prakticke skunosti s nimi nemam, ale mozno by si na nich uz nasiel nieco hotove na tento pripad.

Presne tento problem riesia, ale je to taky koncept, ze na to vsetci kolegovia na zaciatku nadavaju, ze WTF.

vsetci kolegovia na zaciatku nadavaju, ze WTF

Ale ked ich (nas) to uz naucis, tak je dobre :-).

CancelationToken teoreticky nepotrebujes. Ked konzument prestane enumerovat (prerusi foreach), tak aj LongRunningTask sa skoci. Neviem akurat zrusit ten Task.Delay…

Action<ProgressStatus> vs EventHandler - toto neriesi nic. Memory leak je presne rovnaky.

Reactive Extensions riesi memory leak ciastocne a sice ze existuju auto-unsubscribing observables.

Vyuzivam ich vo velkom, ale zrovna na reportovanie progresu by som to nepouzil. Mozno na konzumaciu ProgressChangedEventu:

Observable.FromEvent(e => someService.LongRunningProgress += e, ...)
    .TakeUntil(this.DisposedObservable)
    .Subscribe(ShowProgress);