Módulos asíncronos actuales integrados en Node.Net:

Templates (introducción)

Acabo de portear mi sistema de templates similar a Django y Twig a asíncrono. El sistema de templates lo hice inicialmente para CSharpUtils y era síncrono (aunque empecé a soportar asíncrono, pero no llegué a terminarlo en su momento).

AsyncCache

https://github.com/soywiz/NodeNet/blob/master/NodeNetAsync.Tests/Utils/AsyncCacheTest.cs

También he añadido un sistema de caché asíncrono AsyncCache<TKey, TValue>. Lo utilizo en el filtro para servir estáticos y ahora también en el sistema de templates.
El funcionamiento es muy sencillo: se instancia el objeto y se lanzan peticiones GetAsync con una clave y un delegado que generará el valor a cachear en el caso de que no esté ya en la caché. La ventaja que tiene es que si la clave ya se está generando, el GetAsync espera a que el otro generador termine, evitando race conditions y evitando ejecutar el generador varias veces.

Ejemplo:

AsyncCache<string, byte[]> CachedFiles = new AsyncCache<string, byte[]>();

async public Task<byte[]> GetCachedFile(string FileName) {
 return await CachedFiles.GetAsync(FileName, () =>
 {
  return FileSystem.ReadAllBytesAsync("BasePath/" + Path.GetFileName(FileName));
 });
}  

En este ejemplo aunque se llame dos veces a GetCachedFile mientras uno de ellos está leyendo los bytes del disco duro, en la segunda llamada se esperará a que se genere el valor.

Templates (más detalles)

https://github.com/soywiz/NodeNet/blob/master/NodeNetAsync.Examples/TemplateTestProgram.cs

La forma de usar los templates es la siguiente:

// Creates a TemplateRendered that will read templates from memory (specified with Add method).
var MyTemplateRenderer = await TemplateRenderer.CreateFromMemoryAsync(OutputGeneratedCode: true);

HttpRouter.AddRoute("/", async (Request, Response) =>
{
 Response.Buffering = true;

 await MyTemplateRenderer.WriteToAsync(
  Stream: Response,
  TemplateName: "test",
  Scope: new Dictionary<string, object>() 
  {
   {
    "List", new[] {
     new Item() { Name = "Hello", Age = 3 },
     new Item() { Name = "World", Age = 17 },
     new Item() { Name = "This", Age = 999 },
     new Item() { Name = "Is", Age = -1 },
     new Item() { Name = "A", Age = 0 },
     new Item() { Name = "Test", Age = 33 },
    }
   },
  }
 );
});

public class Item
{
 /// <summary>
 /// Name of the item
 /// </summary>
 public string Name;

 /// <summary>
 /// Age of the item
 /// </summary>
 public int Age;
}

La compilación inicial es bastante lenta. En cuanto esté el Roslyn y la compilación como servicio, este paso será infinitamente más rápido. Pero como se hace una única vez por ejecución y en segundo plano, no es un problema. De todas formas también se podrían cachear los ensamblados para reutilizarse en sucesivas ejecuciones, pero realmente no lo veo necesario.

Y la ejecución es bastante rápida. Sin hacer prácticamente ninguna optimización el ejemplo que he subido me da unos 6000 RPS. Se nota con respecto a los 8000 RPS de un Hello World, pero es asumible y posiblemente sea igual o mejor que en node.js. ¡Y todavía hay mucho por optimizar!

El sistema de templates genera código nativo de C# para que el rendimiento sea lo mejor posible una vez compilado.

A partir del siguiente template:

{% extends '_layout' %}
{% block Content %}
 <h1>Item List</h1>
 <ul>
 {% for Item in List %}
  <li>Name: {{ Item.Name }}, Age: {{ Item.Age }}</li>
 {% endfor %}
 </ul>
{% endblock %}

Se genera el siguiente código asíncrono:

class CompiledTemplate_TempTemplate : TemplateCode {
 public CompiledTemplate_TempTemplate(TemplateFactory TemplateFactory = null) : base(TemplateFactory) { }
 
 override public void SetBlocks(Dictionary<String, RenderDelegate> Blocks) {
  SetBlock(Blocks, "Content", Block_Content);
 }
 
 async override protected Task LocalRenderAsync(TemplateContext Context) {
  await SetAndRenderParentTemplateAsync("_layout", Context);
  await Context.Output.WriteAsync("\r\n\t\t\t\t\t");
  await CallBlockAsync("Content", Context);
  
 }
 
 async public Task Block_Content(TemplateContext Context) {
  await Context.Output.WriteAsync("\r\n\t\t\t\t\t\t<h1>Item List</h1>\r\n\t\t\t\t\t\t<ul>\r\n\t\t\t\t\t\t");
  await Context.NewScopeAsync(async delegate() {
   await ForeachAsync(Context, "Item", Context.GetVar("List"), new EmptyDelegate(async delegate() {
    await Context.Output.WriteAsync("\r\n\t\t\t\t\t\t\t<li>Name: ");
    await Context.OutputWriteAutoFilteredAsync(DynamicUtils.Access(Context.GetVar("Item"),"Name"));
    await Context.Output.WriteAsync(", Age: ");
    await Context.OutputWriteAutoFilteredAsync(DynamicUtils.Access(Context.GetVar("Item"),"Age"));
    await Context.Output.WriteAsync("</li>\r\n\t\t\t\t\t\t");
   }));
  });
  await Context.Output.WriteAsync("\r\n\t\t\t\t\t\t</ul>\r\n\t\t\t\t\t");
 }
}