Closed eerhardt closed 2 months ago
Backlog, waiting for customer feedback.
I found out that Blazor Server uses IAsyncEnumerable<ArraySegment<byte>>
in its Hub:
Which means Blazor Server won't work if we keep this limitation.
Given this information, we will need to support this scenario (at least for the server, but we might as well do the client as well). Right now, the only way I can see this working is by using pure reflection to read from the streaming objects.
There is a scenario here we can't solve without a source generator. Say I have a Hub defined like the following:
public class MyHub : Hub
{
public async Task EnumerableValueTypeParameter(IAsyncEnumerable<int> source)
{
await foreach (var item in source)
{
// do something with item
}
}
}
The problem is that in order to invoke the MyHub.EnumerableValueTypeParameter
method on the SignalR server, we need to be able to create an actual IAsyncEnumerable<int>
instance on the server. We need a real instance of this type because we are going to pass it into the user-defined Hub's method. However, it isn't possible to create an instance of this type in native AOT using reflection. For classes / reference types, we can call MakeGenericMethod/Type to dynamically create the concrete object. But for a ValueType, it isn't guaranteed the AOT'd code for the ValueType will exist (and very likely won't exist since we call MakeGenericMethod on our own methods, which never get instantiated for the ValueType). The same applies for a ChannelReader<ValueType>
parameter on the server as well.
For the other 3 cases, we can provide support on native AOT using reflection. The other 3 cases are:
IAsyncEnumerable<TResult> StreamAsync<TResult>
or Task<ChannelReader<TResult>> StreamAsChannelAsync<TResult>
. In this case, since we know the generic type, we can create a generic Channel<TResult>
or have a generic class that implements IAsyncEnumerable<TResult>
. There's no need for reflection/MakeGenericMethod.For .NET 9 we are able to support these 3 cases. For the case on the server where a parameter of a Hub method takes an IAsyncEnumerable/ChannelReader of ValueType, we will continue throwing an exception for PublishAot=true
.
With https://github.com/dotnet/aspnetcore/pull/56079, native AOT support for SignalR client was added. But one scenario that isn't supported is to use "streaming" APIs (
IAsyncEnumerable<T>
andChannelReader<T>
) with ValueTypes. In order to work withIAsyncEnumerable<T>
andChannelReader<T>
we either need to "jump" to a<T>
generic method (which is what it does today, and not supported with native AOT because it can't generate the code up front) or by using reflection to invoke theMoveNextAsync()
andWaitToReadAsync()
methods.It was decided to not support this scenario until we get data that shows we need to implement this in SignalR.
Note that using ValueTypes in these scenarios isn't necessarily a performance gain because the
T
will be boxed (i.e. an allocation will occur) into theStreamItemMessage
in:https://github.com/dotnet/aspnetcore/blob/206b0aeca39d5eb12e55ce4e35ef4c8b9bc63c86/src/SignalR/clients/csharp/Client.Core/src/HubConnection.cs#L856-L858
cc @BrennanConroy