Not recognized due to async when switching to the Blazor page - asynchronous

This is the case when you switch from the page you use async to another page and come back. When I return, async is constantly working on the console and data is coming in, but StateHasChanged() does not seem to be able to update the screen even if it is working or not. Is there a problem that the page cannot be updated normally due to the problem of separating threads? (StateHasChanged() and other functions) In this case, how can I make the existing thread that I want to recognize?
I searched and there was a word about Synchronization Context, would this help?
#page "/push"
<button class="btn btn-primary" #onclick="#subscribe">subscribe</button>
<div>
#foreach (var noti in PushNotifications.notifications)
{
<p>#noti</p>
}
</div>
#code {
public async Task subscribe()
{
...
reply = client.subscribe(subscriptionRequest);
try
{
await foreach (var subscriptionResponse in reply.ResponseStream.ReadAllAsync())
{
Console.WriteLine(subscriptionResponse);
PushNotifications.notifications.Add(subscriptionResponse);
await InvokeAsync(StateHasChanged);
await Task.Delay(500);
}
}
}

Here is some code that demonstrates one way to maintain state between pages with a long running process.
Everything takes place in a DI scoped service. In this case it just gets a list of countries slowly. There's two cancellation mechanisms: a cancellation token or a method to set an bool.
public class DataService
{
public List<string> Countries { get; set; } = new List<string>();
public Action? CountryListUpdated;
public bool Processing { get; private set; }=false;
private bool _cancel = false;
private CancellationToken _cancellationToken = new CancellationToken();
private Task? _task;
public string Message { get; private set; } = "Idle";
public ValueTask GetCountriesAsync(CancellationToken cancellationToken)
{
_cancellationToken = cancellationToken;
_task = this.getCountriesAsync();
return ValueTask.CompletedTask;
}
public ValueTask GetCountriesAsync()
{
_task = this.getCountriesAsync();
return ValueTask.CompletedTask;
}
public async Task getCountriesAsync()
{
this.Processing = true;
this.Message = "Processing";
this.Countries.Clear();
foreach (var country in _countries)
{
this.Countries.Add(country);
this.CountryListUpdated?.Invoke();
await Task.Delay(2500);
if (_cancellationToken.IsCancellationRequested || _cancel)
Debug.WriteLine("GetCountries Cancelled");
}
this.Message = "Processing Complete";
this.Processing = false;
_cancel = false;
this.CountryListUpdated?.Invoke();
}
public void CancelProcessing()
=> _cancel = true;
private List<string> _countries => new List<string> { "UK", "France", "Portugal", "Spain", "Italy", "Germany"};
}
Registered in Program:
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<WeatherForecastService>();
builder.Services.AddScoped<DataService>();
Here's the display page. Note UI updates are event driven.
#page "/"
#inject DataService DataService;
#implements IDisposable
<PageTitle>Index</PageTitle>
<div class="m-2 p-2">
<button class="btn btn-danger" disabled="#(!this.DataService.Processing)" #onclick=this.CancelProcessing>Cancel Processing</button>
<button class="btn btn-primary" disabled="#this.DataService.Processing" #onclick=GetData>Get Data</button>
</div>
<div class="alert alert-primary">#this.DataService.Message</div>
#foreach (var country in DataService.Countries)
{
<div>#country</div>
}
#code {
private CancellationTokenSource cancellationToken = new CancellationTokenSource();
protected override void OnInitialized()
=> this.DataService.CountryListUpdated += OnCountryUpdated;
private async Task GetData()
{
await this.DataService.GetCountriesAsync(cancellationToken.Token);
}
private void CancelProcessing()
{
//DataService.CancelProcessing();
this.cancellationToken.Cancel();
}
private void OnCountryUpdated()
=> this.InvokeAsync(StateHasChanged);
public void Dispose()
{
// If you want to cancel the processing when you exit a page
//cancellationToken.Cancel();
this.DataService.CountryListUpdated -= OnCountryUpdated;
}
}

... you use async to another page and come back. When I return, async is constantly working on the console [...] , but StateHasChanged() does not seem to be able to update the screen
The most likely explanation is that you 'return' to a new page but the console is still displaying incoming data on the previous old page.
That page is still in memory but not on screen. It should have been cleaned up.
Step 1: When your page code creates resources (like HttpClient) then you need:
#implements IDisposable
#code {
...
public void Dispose()
{
// Dispose resources, make sure your subscription loop is canceled.
_httpClient?.Dispose();
...
}
}
I searched and there was a word about Synchronization Context, would this help?
No, when running on WebAssembly that context is null.

Related

subscribing to Blazor AuthenticationStateChanged

I could not find any example of how to use the AuthenticationStateChanged in blazor.
My intention is that any page where i want to react to user login or logout i will use these
code. I could not find any example on how to implement the event. the one that i tried just keeps on firing for infinite times.
_CustomAuthProvider.AuthenticationStateChanged += AuhtenticationStateChanged;
private async void AuhtenticationStateChanged(Task<AuthenticationState> task)
{
//This keeps on executing in in loop.
}
I know this is old, but I would have liked an answer when I found it...
This is the code I use on a Blazor web assembly (dotnet 6.0). This is part of a scoped service that I can access through dependency injection from anywhere else in my application.
Notice the await(task) to retrieve the state in the event handler:
public AuthenticationService(AuthenticationStateProvider authenticationProvider, IProfileService profileService)
{
_profileService = profileService;
_authenticationProvider = authenticationProvider;
_authenticationProvider.AuthenticationStateChanged += AuthenticationStateChangedHandler;
// perform initial call into the event handler
AuthenticationStateChangedHandler(_authenticationProvider.GetAuthenticationStateAsync());
}
private bool _disposed = false;
public void Dispose()
{
if (!_disposed)
{
_disposed = true;
_authenticationProvider.AuthenticationStateChanged -= AuthenticationStateChangedHandler;
}
}
public event AuthenticationChanged? AuthenticationChanged;
public AuthenticationState? AuthenticationState { get; private set; }
private async void AuthenticationStateChangedHandler(Task<AuthenticationState> task)
{
AuthenticationState = await (task);
if (IsAuthenticated)
{
// first load profile
await _profileService.LoadProfile(UserName!);
}
else
{
await _profileService.EmptyProfile();
}
// then update all listening clients, invoke the event
AuthenticationChanged?.Invoke(AuthenticationState);
}

Make a network call from code behind's constructor Xamarin forms

I am making a network call inside code behind constructor. I have to do this because I am passing parameter to constructor, then using those initiate the network call. Because of I am doing this inside the constructor I have to use Task.Run option which is not a good solution. I tried factory method also but I am unable to use it since passing parameters are done by click event. Can anyone suggest me a better solution. Thanks
Class Home{
private async void HomeLandingListView_ItemTapped(object sender, ItemTappedEventArgs e)
{
var item = (HomeLanding)e.Item;
await Navigation.PushAsync(new PolicyDetailsMotor(item.id, p_end_date));
}
}
Network call class
public partial class PolicyDetailsMotor : ContentPage
{
public PolicyDetailsMotor(string id, string p_end_date)
{
InitializeComponent();
this.id = id;
this.p_end_date = p_end_date;
Title = "Motor Policy Details";
Task.Run(async () =>
{
var result = await api_class.getMotorPolicyDetails(id, p_end_date);
});
}
}

resultCode is 0 for all requests in Application Insights

I have a function app connected with an application insights instance.
When I look at the requests on application insights, all entries have a resultCode of 0, regardless of whether it was successful or not. How can I have the resultCode showing properly?
If I get it correctly, my function app is running at the version "3.0.14916.0".
Here is my startup:
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddLogging(loggingBuilder =>
{
var key = Environment.GetEnvironmentVariable("APPINSIGHTS_INSTRUMENTATIONKEY");
loggingBuilder.AddApplicationInsights(key);
});
builder.Services.AddSingleton(sp =>
{
var key = Environment.GetEnvironmentVariable("APPINSIGHTS_INSTRUMENTATIONKEY");
return new TelemetryConfiguration(key);
});
(...)
}
}
Edit 1:
In the comments it was asked why I am adding logging in Startup. I do it because, as far as I could verify, ILogger < MyClass > only logs to AI if I add logging in Startup.
Following is an example of an injected class. Note that this class is also used in other projects.
public class CosmosDbService : ICosmosDbService
{
private readonly IDocumentClient _documentClient;
private readonly ILogger _logger;
public CosmosDbService(IDocumentClient documentClient, ILogger<CosmosDbService> logger)
{
_logger = logger;
_documentClient = documentClient;
}
public async Task<UserData> GetUserAsync()
{
try
{
// Getting user here
// (...)
}
catch (Exception ex)
{
_logger.LogError(ex, "Error fetching user.");
throw;
}
}
}
This class is injected as:
builder.Services.AddSingleton<IDocumentClient>(sp =>
{
// This does not really matter for this question
var configuration = sp.GetService<IConfiguration>();
var connectionString = configuration.GetValue<string>("COSMOS_DB_CONNECTION");
var cosmosDbConnectionString = new CosmosDbConnectionString(connectionString);
return new DocumentClient(cosmosDbConnectionString.ServiceEndpoint, cosmosDbConnectionString.AuthKey);
});
builder.Services.AddSingleton<ICosmosDbService, CosmosDbService>();
This answer from #PeterBons helped me fixing the wrong resultCode as well.
Basically I was importing the wrong package: Microsoft.Extensions.Logging.ApplicationInsights
I changed it to Microsoft.Azure.WebJobs.Logging.ApplicationInsights and removed the code in Startup. Now I got the resultCode properly filled in again.

Start Service Bus Client from BackgroundService

I have a ServiceBusClient class that creates a QueueClient which is used to listen for messages on a bus. I have looked at the following articles to set this up:
Background tasks (Microsoft)
Hosted services (Microsoft)
Async and Await
My ServiceBusClient class that handles the QueueClient looks like this:
public class ServiceBusClient : IServiceBusClient
{
public ServiceBusClient(IEventService eventService, ServiceBusClientOptions options)
{
...
queueClient = new QueueClient(options.ConnectionString, options.QueueName);
}
public void Run()
{
RegisterOnMessageHandler();
}
private void RegisterOnMessageHandler()
{
...
queueClient.RegisterMessageHandler(ProcessMessagesAsync, messageHandlerOptions);
}
private async Task ProcessMessagesAsync(Message message, CancellationToken token)
{
var eventMessage = EventMessage.FromMessage(message);
await eventService.Write(eventMessage);
if (!token.IsCancellationRequested)
{
await queueClient.CompleteAsync(message.SystemProperties.LockToken);
}
}
private Task ExceptionReceivedHandler(ExceptionReceivedEventArgs exceptionReceivedEventArgs)
{
// log errors
...
return Task.CompletedTask;
}
}
I was hoping to launch from an IHostedService or even by extending the BackgroundService. In the examples I find, work is constantly being executed in a while loop which does not fit my scenario since I am only trying to run a single command.
So I created a super simple implementation like this:
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
serviceBusClient.Run();
while (!cancellationToken.IsCancellationRequested)
{
// empty loop to keep running for lifetime of pod
}
}
If removing the async I obviously need to return something. I tried Task.CompletedTask but that required me to change the return type to Task<Task>.
If I have the async in place, I will need to await something, but I am not sure what.
This does not feel right. I would assume I would need to change something in the ServiceBusClient, but I am unsure what, since the ProcessMessagesAsync is async and does the heavy lifting in the background from my understanding.
All I want is for my web app to start listening for messages until it dies. How can I do that?
I gave up on using BackgroundService and implemented IHostedService instead.
public class MessageListenerService : IHostedService
{
private readonly IServiceBusClient client;
private readonly ITelemetryClient applicationInsights;
public MessageListenerService(IServiceProvider serviceProvider)
{
client = serviceProvider.GetService<IServiceBusClient>();
applicationInsights = serviceProvider.GetService<ITelemetryClient>();
}
public Task StartAsync(CancellationToken cancellationToken)
{
applicationInsights.TrackTrace(new TraceTelemetry("MessageListenerService is starting"));
client.Run();
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
applicationInsights.TrackTrace(new TraceTelemetry("MessageListenerService is stopping"));
return client.Stop();
}
}
If you find issues with this code please let me know in the comments and I'll update as appropriate.
In the end we created a console app for it anyway.

MvvmCross Async command lock

I have alot of button in my application. They are placed next to each other. All of the methods are IMvxAsyncCommand type. I figured out some missmatches after tests done by users. I have found duplicate operations - two diffrent buttons are called in almost same time.
What did I do is created my own SafeAsyncCommand class and inheret from MvxAsyncCommand. My goal is to create delay between executes - I want to prevent double click in given delay in below case 0.5s.
There is my work:
public static class SafeCommandSettings
{
public static bool CanExecute { get; private set; }
public static TimeSpan Delay => TimeSpan.FromMilliseconds(500);
static SafeCommandSettings()
{
CanExecute = true;
}
public static async void Pause()
{
if (!CanExecute) return;
CanExecute = false;
await Task.Delay(Delay);
CanExecute = true;
}
}
public class SafeAsyncCommand : MvxAsyncCommand
{
public SafeAsyncCommand(Func<Task> execute, Func<bool> canExecute = null, bool allowConcurrentExecutions = false)
: base(execute, canExecute, allowConcurrentExecutions)
{
}
public SafeAsyncCommand(Func<CancellationToken, Task> execute, Func<bool> canExecute = null, bool allowConcurrentExecutions = false)
: base(execute, canExecute, allowConcurrentExecutions)
{
}
protected override async Task ExecuteAsyncImpl(object parameter)
{
if (!SafeCommandSettings.CanExecute) return;
SafeCommandSettings.Pause();
await base.ExecuteAsyncImpl(parameter);
}
}
public class SafeAsyncCommand<T> : MvxAsyncCommand<T>
{
public SafeAsyncCommand(Func<T, Task> execute, Func<T, bool> canExecute = null, bool allowConcurrentExecutions = false)
: base(execute, canExecute, allowConcurrentExecutions)
{
}
public SafeAsyncCommand(Func<T, CancellationToken, Task> execute, Func<T, bool> canExecute = null, bool allowConcurrentExecutions = false)
: base(execute, canExecute, allowConcurrentExecutions)
{
}
protected override async Task ExecuteAsyncImpl(object parameter)
{
if (!SafeCommandSettings.CanExecute) return;
SafeCommandSettings.Pause();
await base.ExecuteAsyncImpl(parameter);
}
}
I thought this is working but I saw users were able to do it again. Do I miss some knowledge about async methods or static thread safe?
Thanks in advance
In order to do so you can take advantage of MvxNotifyTask that is a wrapper of the Task that watches for different task states and you'll run on your command and do something like this (notice that you don't need the command to be MvxAsyncCommand):
public MvxNotifyTask MyNotifyTaskWrapper { get; private set; }
public MvxCommand MyCommand { get; private set; }
private void InitializeCommands()
{
// this command is executed only if the task has not started (its wrapper is null) or the task is not in progress (its wrapper is not IsNotCompleted)
this.MyCommand = new MvxCommand(() => this.MyNotifyTaskWrapper = MvxNotifyTask.Create(() => this.MyLogicAsync()),
() => this.MyNotifyTaskWrapper == null || !this.MyNotifyTaskWrapper.IsNotCompleted);
}
private async Task MyLogicAsync()
{
// my async logic
}
So as soon the async process is started the command can't be executed again preventing duplicate operations, and you can start it again when that task completes.
If you have to disable multiple commands execution when running some task just add the same CanExecute condition on the different commands or mix conditions of different MvxNotifyTask
Also check that the MvxNotifyTask raises property-changed notifications that you can subscribe to or bind to in your view displaying a "loading" or something like that when performing the operation.
Note: if you are using Mvx < 5.5 you won't have MvxNotifyTask but you can use NotifyTaskCompletion done by Stephen Cleary that is almost the same as MvxNotifyTask and it is from where MvxNotifyTask was based on.
HIH
Instead of delaying things, consider using AsyncLock by Stephen Cleary or lookup Interlocked.CompareExchange.
As far as I can tell from here, you shouldn't use a static CanExecute in your case, as it locks all commands using your "safe" command at once.
And there is the possibility of race conditions, since you aren't changing the value of CanExecute locked.

Resources