Um membro da comunidade descobriu que era possível rodar .NET no navegador utilizando WebAssembly. A história é longa, mas em resumo parece que ele foi contratado pela Microsoft e está agora trabalhando nisso full time. Digo “parece” porque não tenho certeza, mas tudo indica que é isso mesmo pelos contatos que tive enquanto estive em Redmond para o MVP Summit na semana passada. Esse cara é o Steve Sanderson, e você acha ele no @StevenSanderson.
Nesse meio tempo ele trocou o compilador antigo que estava usando pelo Mono, e a experiência dele ganhou peso, e está bem mais interessante.
A experiência está madura ao ponto de eles liberarem pra testarmos. O projeto, que vai pelo nome de Blazor está disponível pra download e testes. Caso você queira experimentar o Blazor, basta acessar aka.ms/install-blazor e seguir as intruções. Leia esse post no blog de webdev da Microsoft que explica um pouco mais da históra e da tecnologia.
Tive umas conversas sobre o assunto com o Victor Cavalcante e ele não gosta da ideia do Blazor. O ponto principal dele é que o Wasm não foi feito para o fim que o Blazor o está utilizando. E ele tem razão, tanto é que o Wasm não consegue acessar o DOM diretamente. Li o código do Blazor, e pra ele funcionar o que acontece é um bruta hack (nesse momento). Basicamente o que acontece é que o processamento todo acontece nas views Razor no C# (via Wasm) e o resultado é enviado pro JavaScript, que – esse sim pode acessar o DOM – faz os updates necessários no HTML. Além disso, o Victor gosta muito de JavaScript.
Pra mim, além deste problema do uso não planejado do Wasm há outro problema tão grave quanto: o JS é a lingua comum da web, e se passarmos a utilizar outras linguagens e plataformas vamos fragmentar a web. Um componente .NET rodando via Wasm não vai ser reaproveitável por outro escrito em Java, ou Rust, por exemplo. Esse é um problema grave e ainda não resolvido. Seria interessante que ele fosse trabalhado, vamos ver como isso evolui. Uma possibilidade que consigo vislumbrar é a utilização de interfaces de comunicação de baixo nível para comunicação entre as diversas plataformas, permitindo reuso. Mas isso está muito longe da realidade atual.
Quanto ao uso “indevido” do Wasm, isso pode evoluir. Quem sabe no futuro não temos um <script type="wasm">
que acessa o DOM. Mas este também não é o cenário hoje. Isso é pura especulação da minha parte.
E não é do Blazor que quero falar, pelo menos não da parte de usar ele como uma mistura de ASP.NET MVC com React pra renderizar HTML no navegador. Quero usar o WebAssembly para a finalidade que ele foi concebido: executar demandas de processamento que precisam de mais desempenho. A ideia é que JavaScript não é a linguagem ideal pra isso, é interpretada e lenta (comparada com código mais baixo nível como Rust ou C++, ou até Java e C#), e single threaded.
Se você quiser conhecer mais do Blazor, na última sexta-feira lançamos um episódio do podcast tratando do assunto. Ouve lá.
Interoperando entre JavaScript e C#
A ideia era fazer uma aplicação simples em C# e chamar ela do JavaScript. Criei um projeto Blazor do zero. Ele traz um projetinho simples, igual ao exemplo que está na web pra testar o Blazor. Esse aqui.
Daí fui arrancando tudo. Tirei todas as Views, controllers, etc. Sobrou só um arquivo Program.cs
e outro index.html
. Todo o código está online no repositório giggio/tryDotNetOnWasm. Mas vou referenciar as principais partes aqui.
Descobri que é possível chamar uma função do C# a partir do JavaScript. Pra isso, basta usar o método Blazor.platform.findMethod
pra encontrar o método do C# que você quer chamar, em seguida invocá-lo com Blazor.platform.callMethod
. O parâmetro você precisa converter de uma string JavaScript pra uma string .NET, e isso é feito com o método Blazor.platform.toDotNetString
. O método vai retornar um valor, que, pelo que entendi, é o local de memória do retorno de fato. Você recupera o valor com o método Blazor.platform.toJavaScriptString
. Sim, novamente uma string. E eu só consegui invocar o C# com strings, e recuperar o retorno com strings. Não consegui passar números, datas, etc. Isso quer dizer que todos os valores tem que ficar sendo convertidos. Mas você pode tenta passar um json e deserializar, pra passar estruturas mais complexas.
Veja abaixo como fica a chamada do método Fatorial
da classe Blazor.Program
:
let m = Blazor.platform.findMethod("Blazor3", "Blazor3", "Program", "Fatorial"); let res = Blazor.platform.callMethod(m, null, [Blazor.platform.toDotNetString(numero)]) const resultado = Blazor.platform.toJavaScriptString(res);
Também consegui chamar o JavaScript a partir do C#. Pra isso, você precisa registrar a função no JavaScript utilizando o método Blazor.registerFunction
, dessa forma:
Blazor.registerFunction("funcao", param => console.log(`funcao: ${param}`));
Pra chamar a função você faz assim no C#:
Blazor.Browser.Interop.RegisteredFunction.Invoke<object>("funcao", "Hello from c#");
Bem mais simples ir do C# pro JavaScript do que o contrário. De qualquer forma, imagino que esse tipo de coisa vai melhorar. O código do Blazor está cheio de anotações apontando melhorias e problemas a serem corrigidos, claramente é um código em evolução.
Tentei adicionar uma referência à uma biblioteca .NET Standard de tratamento de imagens, e ela não funcionou. Ela era implementada 100% em código gerenciado, sem dependências externas, e mesmo assim só recebi exceptions. E as exceptions não são exatamente as mais claras. Por exemplo, em algum momento o Blazor simplesmente não carregava mais a dll do meu projeto por que uma exception estava sendo lançada, e o erro não ajudava em nada a entender o problema.
Debug
O debug do C# também é inexistente nesse momento. A única forma que encontrei para resolver os problemas é o famoso passei por aqui
no meio do código C#. A cada Console.WriteLine
que você faz no C# aparece uma mensagem na console JavaScript do navegador. Funciona, mas parece que estamos de volta à decada de 80.
Desempenho
O grande argumento pro uso do C#, além de funcionalidades da linguagem e compartilhamento de código entre o front end e o back end, é o desempenho. O Wasm foi criado pra isso. Avaliei se no estado atual do Blazor o desempenho dele é capaz de bater o JavaScript, e não é. Ele é pelo menos seis vezes mais lento do que o JavaScript em todos os casos, chegando a ser 77x mais lento no pior caso. Não sei a que atribuir esses valores, mas a realidade é essa. Imagino que o código JS está com menos overhead no momento. Nos meus testes, que estão no mesmo repo que mencionei acima, eu rodei o fatorial do número 12 um milhão de vezes. Como os compiladores são espertos e memorizam o resultado das funções, eu introduzi uma certa aleatoriedade pra ver se o C# conseguiria alcançar o JavaScript, e quando isso acontece a distância entre eles aumenta. O C# foi rodado em uma única thread, e o JS não tem a opção de ter mais de uma thread.
Eu testei também com multiplas threads, tanto usando Task
quando a TPL. Ambas travam a execução e não retornam. A impressão que dá é que o controle de threads é tão problemático no momento que está muito ineficiente.
Resumindo, o argumento de desempenho para uso do .NET via Wasm, no estado atual, simplesmente não existe. Vamos aguardar e ver se isso melhora.
Abaixo a tabela com os resultados, quanto menor os valores, melhores, e resultados sempre em milisegundos:
Desempenho | Chrome | Firefox |
---|---|---|
Desempenho C# | 1497 | 926 |
Desempenho JS | 20 | 12 |
Desempenho C# não otimizado | 10.003 | 6.608 |
Desempenho JS não otimizado | 845 | 1.020 |
Curiosidade: note que o Firefox é mais rápido, com exceção do último teste.
Conclusão
O Blazor é um experimento legal e divertido, mas ainda está muito no começo. Nem mesmo o propósito de desempenho se justifica no momento. Não é a hora de fazer nada sério com .NET via Wasm, é hora de brincar com esses experimentos, pelo puro prazer de descobrir o que é possível e ver a história acontecendo. Use-o com esse propósito, e vai valer a pena.
Volto com updates do .NET via Wasm assim que o Blazor der o próximo passo. Desde que não seja pro cemitério, que é um destino possível.
Giovanni Bassi
Arquiteto e desenvolvedor, agilista, escalador, provocador. É fundador e CSA da Lambda3. Programa porque gosta. Acredita que pessoas autogerenciadas funcionam melhor e por acreditar que heterarquia é mais eficiente que hierarquia. Foi reconhecido Microsoft MVP há mais de dez anos, dos mais de vinte que atua no mercado. Já palestrou sobre .NET, Rust, microsserviços, JavaScript, TypeScript, Ruby, Node.js, Frontend e Backend, Agile, etc, no Brasil, e no exterior. Liderou grupos de usuários em assuntos como arquitetura de software, Docker, e .NET.