I'm learning to work with Blazor WebAssembly and SignalR. I want to have single WebSocket connection (therefore a single HubConnection instance) and share that between all the Blazor components that need to do something over SignalR WebSockets.
I want to call HubConnection.StreamAsync() and then loop over the generated stream as the data comes in (it's an endless stream). I also want it to cancel the stream when component is not shown anymore.
I have 2 problems with this idea:
Only one component is capable of streaming values. Other components get stuck on "await foreach" and never receive any items.
During component's dispose, i trigger the cancellation token, but the server side doesn't receive a cancellation. Only when i refresh the whole browser, the cancellation is triggered.
When i traverse to any other Blazor page and then return to the page where my streaming components are, now no component ever receives items.
I have a vague hunch that those 2 things are somehow connected - somehow on server side, a single hub can only really respond to single StreamAsync() call in single connection?
In the end, i've been unable to find a solution for this. What am i doing wrong? Maybe any of you could help me out on this?
Code
To reproduce, you may follow the instructions:
Start with Blazor WebAssembly with ASP.NET MVC backend project. I use .net core 6, most nugets are with version 6.0.7. I have also installed nuget Microsoft.AspNetCore.SignalR.Client on Blazor Client project.
Do the following changes/updates:
Client - App.razor
Add this code
#using Microsoft.AspNetCore.SignalR.Client
#implements IAsyncDisposable
#inject HubConnection HubConnection
...
#code {
private CancellationTokenSource cts = new();
protected override void OnInitialized()
{
base.OnInitialized();
HubConnection.Closed += error =>
{
return ConnectWithRetryAsync(cts.Token);
};
_ = ConnectWithRetryAsync(cts.Token);
}
private async Task<bool> ConnectWithRetryAsync(CancellationToken token)
{
// Keep trying to until we can start or the token is canceled.
while (true)
{
try
{
await HubConnection.StartAsync(token);
return true;
}
catch when (token.IsCancellationRequested)
{
return false;
}
catch
{
// Try again in a few seconds. This could be an incremental interval
await Task.Delay(5000);
}
}
}
public async ValueTask DisposeAsync()
{
cts.Cancel();
cts.Dispose();
await HubConnection.DisposeAsync();
}
}
Client - Program.cs
Add the following singleton to DI
builder.Services.AddSingleton(sp =>
{
var navMan = sp.GetRequiredService<NavigationManager>();
return new HubConnectionBuilder()
.WithUrl(navMan.ToAbsoluteUri("/string"))
.WithAutomaticReconnect()
.Build();
});
Client - Create a component called "StringDisplay"
#using Microsoft.AspNetCore.SignalR.Client
#inject HubConnection HubConnection
#implements IDisposable
#if(currentString == string.Empty)
{
<i>Loading...</i>
}
else
{
#currentString
}
#code {
private string currentString = string.Empty;
private CancellationTokenSource cts = new();
protected override void OnInitialized()
{
base.OnInitialized();
_ = Consumer();
}
protected override void OnParametersSet()
{
base.OnParametersSet();
_ = Consumer();
}
private async Task Consumer()
{
try
{
cts.Cancel();
cts.Dispose();
cts = new();
var stream = HubConnection.StreamAsync<string>("GetStrings", cts.Token);
await foreach(var str in stream)
{
if(cts.IsCancellationRequested)
break;
currentString = str;
StateHasChanged();
}
}
catch(Exception e)
{
Console.WriteLine(e);
}
}
public void Dispose()
{
cts.Cancel();
cts.Dispose();
}
}
Client - Index.razor
Add the StringDisplay component 3 times onto the page:
<hr />
<StringDisplay /><hr />
<StringDisplay /><hr />
<StringDisplay /><hr />
Server - Create StringGeneratorService.cs
namespace BlazorWebAssembly.Server.Services;
public class StringGeneratorService
{
private readonly PeriodicTimer _timer;
public event Action<string>? OnGenerated;
public StringGeneratorService()
{
_timer = new PeriodicTimer(TimeSpan.FromMilliseconds(200));
Task.Run(TimerRunnerAsync);
}
private async Task TimerRunnerAsync()
{
while (true)
{
await _timer.WaitForNextTickAsync();
var str = Guid.NewGuid().ToString();
OnGenerated?.Invoke(str);
}
}
}
Server - Create StringHub.cs
using BlazorWebAssembly.Server.Services;
using Microsoft.AspNetCore.SignalR;
using System.Runtime.CompilerServices;
namespace BlazorWebAssembly.Server.Hubs
{
public class StringHub : Hub
{
private readonly StringGeneratorService _generatorService;
public StringHub(StringGeneratorService generatorService)
{
_generatorService = generatorService;
}
public async IAsyncEnumerable<string> GetStrings([EnumeratorCancellation] CancellationToken cancellationToken)
{
using var flag = new AutoResetEvent(false);
string currentString = string.Empty;
var listener = (string str) => { currentString = str; flag.Set(); };
_generatorService.OnGenerated += listener;
cancellationToken.Register(() =>
{
_generatorService.OnGenerated -= listener;
});
while (!cancellationToken.IsCancellationRequested)
{
flag.WaitOne();
yield return currentString;
}
yield break;
}
}
}
Server - Program.cs
Register necessary parts
builder.Services.AddSingleton<StringGeneratorService>();
...
app.MapHub<StringHub>("/string");
Related
I've created a new ASP.NET 6 web app.
I want to periodically broadcast a message through a SignalR hub, from the server.
How can I access the hub from the server? Other answers suggest using GlobalHost but it belongs to a deprecated version of SignalR
Example code from web app Program.cs:
app.MapHub<SiteLayoutHub>("hubs/site");
app.Run();
Task.Factory.StartNew(async () =>
{
var hub = GetSiteLayoutHub(); // How can I get this hub?
while (true)
{
var uiState = GetUIState();
await hub.SendUIUpdateMessage(uiState);
Thread.Sleep(500);
}
});
SiteLayoutHub.cs:
public class SiteLayoutHub : Hub
{
public async Task SendUIUpdateMessage(UIState uiState)
{
await Clients.All.SendAsync("UIUpdateMessage", uiState);
}
}
These are all of the pieces required:
using Microsoft.AspNetCore.SignalR;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSignalR();
builder.Services.AddHostedService<UIUpdateService>();
var app = builder.Build();
app.MapHub<SiteLayoutHub>("hubs/site");
app.Run();
public class SiteLayoutHub : Hub { }
public class UIUpdateService : BackgroundService
{
private readonly IHubContext<SiteLayoutHub> _hubContext;
public UIUpdateService(IHubContext<SiteLayoutHub> hubContext)
{
_hubContext = hubContext;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var timer = new PeriodicTimer(TimeSpan.FromSeconds(5));
while (await timer.WaitForNextTickAsync(stoppingToken))
{
var uiState = GetUiState();
await _hubContext.Clients.All.SendAsync("UIState", uiState);
}
}
private object GetUiState()
{
throw new NotImplementedException();
}
}
How to make a webApi a Windows service by adding Start, Stop and Restart event handling in .Net 6?
In .Net 5 and before that, I usually make an AppStartup like this:
public interface IAppStartup
{
Task StartAsync();
Task StopAsync();
Task RestartService(IHostedService hostedService);
}
public class AppStartup : IAppStartup
{
private readonly string[] _args;
private readonly CancellationTokenSource _cancellationTokenSource = new();
private IHost _host;
private static AppStartup _appStartup;
private AppStartup(string[] args)
{
_args = args;
}
public async Task StartAsync()
{
try
{
_host = CreateHostBuilder(_args).Build();
var setting = _host.Services.GetService<AppSettings>();
if (setting != null)
{
///
}
await _host.RunAsync(_cancellationTokenSource.Token);
}
catch (Exception e)
{
///
throw;
}
}
public async Task StopAsync()
{
try
{
await _host.StopAsync(_cancellationTokenSource.Token);
}
catch (Exception e)
{
///
throw;
}
}
public async Task RestartService(IHostedService hostedService)
{
if (hostedService!=null)
{
await hostedService.StopAsync(_cancellationTokenSource.Token);
await hostedService.StartAsync(_cancellationTokenSource.Token);
}
}
}
Now in .Net 6, I am confused because there is no Startup method anymore to call.
Please kindly advise me of any solution you use to add Windows service functionalities into a web API project.
Both .NET 5 and .NET 6 have support for the Windows Service lifetime, which hooks into Start/Stop for you. So it's essentially just this in your Program.cs:
host = CreateHostBuilder(args)
.UseWindowsService(...)
.Build();
var setting = host.Services.GetService<AppSettings>();
if (setting != null)
{
///
}
await host.RunAsync();
I am working with Azure Event Hub producer client and reading messages off of a kafka stream then pass it along to deserialize/map, then pass to Event Hub. I have the consume loop which is creating a task for each consume and then two methods to do processing(this seems to have greatly improved the speed from the kafka lag perspective. However, Event hub makes you create an event batch which I don't necessarily want to use. I just want to send the data one message at a time for now. In order to create a new batch I have to call Dispose(). I am running into an issue where there's another call to the function by the the time I call Dispose() and I get an error saying the object is being used by event hub.
I've also tried using the overload for eventHubProducerClient.SendAsync that allows you to pass in a IEnumerable but i'm running into the same issue with that.
So I believe this to be a synchronization issue, or maybe I need to do a lock somewhere?
Any help would be appreciated.
public void Execute()
{
using (_consumer)
{
try
{
_consumer.Subscribe(_streamConsumerSettings.Topic);
while (true)
{
var result = _consumer.Consume(1000);
if (result == null)
{
continue;
}
var process = Task.Factory.StartNew(() => ProcessMessage(result?.Message?.Value));
var send = process.ContinueWith(t => SendMessage(process.Result));
}
}
catch (ConsumeException e)
{
_logger.LogError(e, e.StackTrace ?? e.Message);
_cancelConsume = true;
_consumer.Close();
RestartConsumer();
}
}
}
public static EquipmentJson ProcessMessage(byte[] result)
{
var json = _messageProcessor.DeserializeAndMap(result);
return json;
}
public static void SendMessage(EquipmentJson message)
{
try
{
_eventHubClient.AddToBatch(message);
}
catch (Exception e)
{
_logger.LogError(e, e.StackTrace ?? e.Message);
}
}
public async Task AddToBatch(EquipmentJson message)
{
if
(!string.IsNullOrEmpty(message.EquipmentLocation))
{
try
{
var batch = await _equipmentLocClient.CreateBatchAsync();
batch.TryAdd(new EventData(Encoding.UTF8.GetBytes(message.EquipmentLocation)));
await _eventHubProducerClient.SendAsync(batch);
batch.Dispose();
_logger.LogInformation($"Data sent {DateTimeOffset.UtcNow}");
}
catch (Exception e)
{
_logger.LogError(e, e.StackTrace ?? e.Message);
}
}
}
public class EventHubClient : IEventHubClient
{
private readonly ILoggerAdapter<EventHubClient> _logger;
private readonly EventHubClientSettings _eventHubClientSettings;
private IMapper _mapper;
private static EventHubProducerClient _equipmentLocClient;
public EventHubClient(ILoggerAdapter<EventHubClient> logger, EventHubClientSettings eventHubClientSettings, IMapper mapper)
{
_logger = logger;
_eventHubClientSettings = eventHubClientSettings;
_mapper = mapper;
_equipmentLocClient = new EventHubProducerClient(_eventHubClientSettings.ConnectionString, _eventHubClientSettings.EquipmentLocation);
}
}
}
Based on my speculation in comments, I'm curious if refactoring to use async/await rather than the explicit continuation in the main loop may help. Perhaps something similar to the following LinqPad snippet:
async Task Main()
{
while (true)
{
var message = await Task.Factory.StartNew(() => GetText());
var events = new[] { new EventData(Encoding.UTF8.GetBytes(message)) };
await Send(events).ConfigureAwait(false);
}
}
public EventHubProducerClient client = new EventHubProducerClient("<< CONNECTION STRING >>");
public async Task Send(EventData[] events)
{
try
{
await client.SendAsync(events).ConfigureAwait(false);
"Sent".Dump();
}
catch (Exception ex)
{
ex.Dump();
}
}
public string GetText()
{
Thread.Sleep(250);
return "Test";
}
If you're set on keeping the continuation, I wonder if a slight structural refactoring in the continuation may help, both to push up creation of the events and to honor the await statements. Perhaps something similar to the following LinqPad snippet:
async Task Main()
{
while(true)
{
var t = Task.Factory.StartNew(() => GetText());
var _ = t.ContinueWith(async q =>
{
var events = new[] { new EventData(Encoding.UTF8.GetBytes(t.Result)) };
await Send(events).ConfigureAwait(false);
});
await Task.Yield();
}
}
public EventHubProducerClient client = new EventHubProducerClient("<< CONNECTION STRING >>");
public async Task Send(EventData[] events)
{
try
{
await client.SendAsync(events).ConfigureAwait(false);
"Sent".Dump();
}
catch (Exception ex)
{
ex.Dump();
}
}
public string GetText()
{
Thread.Sleep(250);
return "Test";
}
Sorry, if this is a stupid question but I don't find any useful information in the internet.
Has anyone ever tried to implement the observer pattern in C# using gRPC as communication?
If yes, please show me the link.
Many thanks in advance and best regards.
I have implemented a client convenience class wrapper to turn server streaming calls into regular events for a project I am working. Not sure if this is what you are after. Here is a simple gRPC server that just publishes the time as a string once every second.
syntax = "proto3";
package SimpleTime;
service SimpleTimeService
{
rpc MonitorTime(EmptyRequest) returns (stream TimeResponse);
}
message EmptyRequest{}
message TimeResponse
{
string time = 1;
}
The server implementation, which just loops once a second returning the string representation of the current time until canceled, is as follows
public override async Task MonitorTime(EmptyRequest request, IServerStreamWriter<TimeResponse> responseStream, ServerCallContext context)
{
try
{
while (!context.CancellationToken.IsCancellationRequested)
{
var response = new TimeResponse
{
Time = DateTime.Now.ToString()
};
await responseStream.WriteAsync(response);
await Task.Delay(1000);
}
}
catch (Exception)
{
Console.WriteLine("Exception on Server");
}
}
For the client, I created a class that contains the gRPC client and exposes the results of the server streaming MonitorTime call as a plain ole .net event.
public class SimpleTimeEventClient
{
private SimpleTime.SimpleTimeService.SimpleTimeServiceClient mClient = null;
private CancellationTokenSource mCancellationTokenSource = null;
private Task mMonitorTask = null;
public event EventHandler<string> OnTimeReceived;
public SimpleTimeEventClient()
{
Channel channel = new Channel("127.0.0.1:50051", ChannelCredentials.Insecure);
mClient = new SimpleTime.SimpleTimeService.SimpleTimeServiceClient(channel);
}
public void Startup()
{
mCancellationTokenSource = new CancellationTokenSource();
mMonitorTask = Task.Run(() => MonitorTimeServer(mCancellationTokenSource.Token));
}
public void Shutdown()
{
mCancellationTokenSource.Cancel();
mMonitorTask.Wait(10000);
}
private async Task MonitorTimeServer(CancellationToken token)
{
try
{
using (var call = mClient.MonitorTime(new SimpleTime.EmptyRequest()))
{
while(await call.ResponseStream.MoveNext(token))
{
var timeResult = call.ResponseStream.Current;
OnTimeReceived?.Invoke(this, timeResult.Time);
}
}
}
catch(Exception e)
{
Console.WriteLine($"Exception encountered in MonitorTimeServer:{e.Message}");
}
}
}
Now create the client and subscribe to the event.
static void Main(string[] args)
{
SimpleTimeEventClient client = new SimpleTimeEventClient();
client.OnTimeReceived += OnTimeReceivedEventHandler;
client.Startup();
Console.WriteLine("Press any key to exit");
Console.ReadKey();
client.Shutdown();
}
private static void OnTimeReceivedEventHandler(object sender, string e)
{
Console.WriteLine($"Time: {e}");
}
Which when run produces
I have left out a lot of error checking and such to make the example smaller. One thing I have done is for gRPC interfaces with many server streaming calls that may or may not be of interest to call clients, is to implement the event accessor (add,remove) to only call the server side streaming method if there is a client that has subscribed to the wrapped event. Hope this is helpful
I'm working on a little console application that selects messages from a database queue
and forwards the messages to a rest api (ASP.NET Web Api).
In general the application does the following steps:
Get the number of pending messages
Load the last pending messages
Post the message to the rest api
Remove the message from the database
To make the program more flexible and to have the ability to process every single message
in a separate database transcation the steps 2 - 3 will be executed as tasks.
This means if there are four messages in the database, we'll have four tasks that
should run nearly parallel and process the messages.
This is what the code looks like:
Database message
public class DatabaseMessage
{
public string Message { get; set; }
}
UnitOfWork (Interface IUnitOfWork)
public class UnitOfWork
{
// ... extermely simplified
public int GetNumberOfPendingMessages() { ... }
public DatabaseMessage GetNextPendingMessage() { ... }
public void DeleteMessage(DatabaseMessage message) { ... }
}
HttpService (Interface IHttpService)
public class HttpService
{
private readonly HttpClient _httpClient;
public HttpService()
{
_httpClient = new HttpClient();
/* Some initalization stuff for the HttpClient object */
}
public async Task<HttpResponse> PostMessage(DatabaseMessage message)
{
var content = /* Create content object */
return await _httpClient.PostAsync(..., content);
}
}
MessageProcessingService (Interface IMessageProcessingService)
public class MessageProcessingService
{
private readonly IHttpService _httpService;
private readonly Semaphore _databaseProcessingSemaphore;
public MessageProcessingService(IHttpService httpService)
{
_httpService = httpService;
}
public async Task ProcessDatabaseMessages()
{
var unitOfWork = new UnitOfWork();
var numberOfPendingMessages = unitOfWork.GetNumberOfPendingMessages();
var messageProcessingTasks = new List<Task>();
for(int t = 0; t < numberOfPendingMessages; t++)
{
messageProcessingTasks.Add(new Task(() => {
ProcessMessageAsTask();
}));
}
var continuationHandler = Task.WhenAll(messageProcessingTasks);
messageProcessingTasks.ForEach(e => e.Start());
await continuationHandler;
}
private void ProcessMessageAsTask()
{
// Single unit of work for each tasks
var unitOfWork = new UnitOfWork();
try{
// Starting a database transaction
unitOfWork.StartTransaction();
_databaseProcessingSemaphore.OnWait();
var message = unitOfWork.GetNextPendingMessage();
_databaseProcessingSemaphore.Release();
if(message != null)
{
var response = _httpService.PostMessage(message).Result;
if(response == HttpStatus.OK)
{
unitOfWork.DeleteMessage(message);
unitOfWork.Commit();
}
else
{
unitOfWork.Rollback();
}
}
else
{
unitOfWork.Commit();
}
}
catch(Exception ex)
{
unitOfWork.Rollback();
// Further error handling...
}
}
}
For better understanding, the HttpClient object is created and managed by Unity and is injected
into the MessageProcessingService object. The HttpClient is held as singleton in the container.
I'm facing now the problem that the call of the method _httpService.PostMessage(). For example, if there are five
messages in the message queue, the call fails five times with an exception that tells me that an task has been canceled.
My question is now what is the problem with PostAsync call of the .NET HttpClient? Is the issue caused by the .Result option or would
it be better to create a new instance of the HttpClient for each message processing task?
Or is there a general problem with the architecture with tasks and the processing of rest api calls?
Update 2018-04-04 - 08:09
I've now made the method ProcessMessageAsTask async and I'm awaiting now the call of the HttpService.
But now I don't get any exception at all. In the ressource monitor and while debugging I can see that all tasks reach the call of the HttpClient (return await _httpClient.PostAsync(..., content);)
But there is no exception nor will the messages be posted. But I don't get any exceptions. The program will be closed immediately after the calls of the HttpClient. All futher statements were not processed.
Changes:
public async Task ProcessDatabaseMessages()
{
var unitOfWork = new UnitOfWork();
var numberOfPendingMessages = unitOfWork.GetNumberOfPendingMessages();
var messageProcessingTasks = new List<Task>();
for(int t = 0; t < numberOfPendingMessages; t++)
{
messageProcessingTasks.Add(new Task(async () => {
await ProcessMessageAsTask();
}));
}
var continuationHandler = Task.WhenAll(messageProcessingTasks);
messageProcessingTasks.ForEach(e => e.Start());
await continuationHandler;
}
private async Task ProcessMessageAsTask()
{
// Single unit of work for each tasks
var unitOfWork = new UnitOfWork();
try{
// Starting a database transaction
unitOfWork.StartTransaction();
_databaseProcessingSemaphore.OnWait();
var message = unitOfWork.GetNextPendingMessage();
_databaseProcessingSemaphore.Release();
if(message != null)
{
var response = await _httpService.PostMessage(message);
if(response == HttpStatus.OK)
{
unitOfWork.DeleteMessage(message);
unitOfWork.Commit();
}
else
{
unitOfWork.Rollback();
}
}
else
{
unitOfWork.Commit();
}
}
catch(Exception ex)
{
unitOfWork.Rollback();
// Further error handling...
}
}