Is it safe to call StateHasChanged() from an arbitrary thread? - c#

Is it safe to call StateHasChanged() from an arbitrary thread?
Let me give you some context. Imagine a Server-side Blazor/Razor Components application where you have:
A singleton service NewsProvider that raises BreakingNews events from an arbitrary thread.
A component News.cshtml that gets the service injected and subscribes to BreakingNews event. When the event is raised, the component updates the model and calls StateHashChanged()
NewsProvider.cs
using System;
using System.Threading;
namespace BlazorServer.App
{
public class BreakingNewsEventArgs: EventArgs
{
public readonly string News;
public BreakingNewsEventArgs(string news)
{
this.News = news;
}
}
public interface INewsProvider
{
event EventHandler<BreakingNewsEventArgs> BreakingNews;
}
public class NewsProvider : INewsProvider, IDisposable
{
private int n = 0;
public event EventHandler<BreakingNewsEventArgs> BreakingNews;
private Timer timer;
public NewsProvider()
{
timer = new Timer(BroadCastBreakingNews, null, 10, 2000);
}
void BroadCastBreakingNews(object state)
{
BreakingNews?.Invoke(this, new BreakingNewsEventArgs("Noticia " + ++n));
}
public void Dispose()
{
timer.Dispose();
}
}
}
News.cshtml
#page "/news"
#inject INewsProvider NewsProvider
#implements IDisposable
<h1>News</h1>
#foreach (var n in this.news)
{
<p>#n</p>
}
#functions {
EventHandler<BreakingNewsEventArgs> breakingNewsEventHandler;
List<string> news = new List<string>();
protected override void OnInit()
{
base.OnInit();
breakingNewsEventHandler = new EventHandler<BreakingNewsEventArgs>(OnBreakingNews);
this.NewsProvider.BreakingNews += breakingNewsEventHandler;
}
void OnBreakingNews(object sender, BreakingNewsEventArgs e)
{
this.news.Add(e.News);
StateHasChanged();
}
public void Dispose()
{
this.NewsProvider.BreakingNews -= breakingNewsEventHandler;
}
}
Startup.cs
using Microsoft.AspNetCore.Blazor.Builder;
using Microsoft.Extensions.DependencyInjection;
using BlazorServer.App.Services;
namespace BlazorServer.App
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Since Blazor is running on the server, we can use an application service
// to read the forecast data.
services.AddSingleton<WeatherForecastService>();
services.AddSingleton<INewsProvider, NewsProvider>();
}
public void Configure(IBlazorApplicationBuilder app)
{
app.AddComponent<App>("app");
}
}
}
it apparently works, but I don't know if StateHasChanged() is thread safe. If it isn't, how can I call StateHashChanged() safely?. Is there something similar to Control.BeginInvoke? Should I use SyncrhonizationContext.Post?

No, calling StateHasChanged() from an arbitrary thread is not safe.
The correct way to call StateHasChanged() is by using InvokeAsync()
void OnBreakingNews(object sender, BreakingNewsEventArgs e)
{
InvokeAsync(() => {
news.Add(e.News);
StateHasChanged();
});
}

Related

How can I use I timer in my Blazor server app, to perform an action periodically?

Hello I have a Blazor server app where some PLC variables are read from a remote production machine.
The code part that is connecting to the PLC and reading the datas is in a sourced service (PLCService.cs). The service is injected and called in a razor page.
If I trigger the reading manualy with a button, all the variables are read correctly. So far no problem. But I want that the variables are read every second from the remote machine's PLC. Therefore I have programmed a timer in the codebehind page of my razor page (not in the service), but the timer is not working. (The values are note read even once)
In the razor page there are also the variables that I read from the PLC, but for making it leaner I have just shown the counter value, that should count each second.
In my razor file:
#inject PLCService PLCService
<button #onclick="Read_CNC_Status">Read PLC Data</button>
<l>#counter_timer</l> // Unfortunately the counter value is always "0"
In my razor.cs file:
using System.Timers;
public partial class Read_PLC_Data
{
public int counter_timer=0;
System.Timers.Timer timer1 = new System.Timers.Timer();
public async void Read_CNC_Status()
{
PLCService.Connect_PLC(); // Connection code is in a sourced service
Initialise_Timer1();
}
public void Initialise_Timer1()
{
timer1.Elapsed += new ElapsedEventHandler(OnTimedEvent1);
timer1.Interval = 1000;
timer1.Enabled = true;
counter_timer = 0;
}
public void OnTimedEvent1(object source, ElapsedEventArgs e)
{
PLCService.Read_PLC_Data();
counter_CNC_status += 1; // This counter is not counting !!!
if(counter_timer >= 30)
{
counter_timer = 0;
timer1.Enabled = false;
}
StateHasChanged();
}
}
Try the periodic timer:
private readonly PeriodicTimer _periodicTimer = new(TimeSpan.FromSeconds(5));
public async void OnGet()
{
while(await _periodicTimer.WaitForNextTickAsync())
{
await ConnectPlc();
}
}
UPDATE 1
If you want to go with your approach you should use InvokeAsync(StateHasChanged) because Blazor would not recognize the state change and not refresh the UI
<h3>#_currentCount</h3>
#code {
private int _currentCount;
private System.Timers.Timer _timer;
protected override void OnInitialized()
{
_timer = new();
_timer.Interval = 1000;
_timer.Elapsed += async (object? sender, ElapsedEventArgs e) =>
{
_currentCount++;
await InvokeAsync(StateHasChanged);
};
_timer.Enabled = true;
}
}
Don't forget to invoke the StateHasChanged method
UPDATE 2
If you want to use the periodic timer that i initially suggest, you can use it this way:
First let's assume that you have a class that is responsible for PLC data and implements the INotifyPropertyChanged interface:
public class PlcData : INotifyPropertyChanged
{
private readonly ILogger<PlcData> _logger;
public event PropertyChangedEventHandler? PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private int _counter;
public int Counter
{
get { return _counter; }
set
{
if (_counter != value)
{
_counter = value;
NotifyPropertyChanged(nameof(Counter));
}
}
}
public PlcData(ILogger<PlcData> logger)
{
_logger = logger;
}
public async Task GetFromPlcAsync()
{
_logger.LogInformation("Get new info: {c}", ++Counter);
await Task.CompletedTask;
}
}
Then Create a background service
public class PlcService : BackgroundService
{
private readonly PlcData _plc;
private readonly PeriodicTimer _timer;
public PlcService(PlcData plc)
{
_plc = plc;
_timer = new(TimeSpan.FromSeconds(1));
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while(await _timer.WaitForNextTickAsync(stoppingToken)
&& !stoppingToken.IsCancellationRequested)
{
await _plc.GetFromPlcAsync();
}
}
}
In razor page your need to inject the PlcData
#inject PlcData plcData
<h3>#_currentCount</h3>
#code {
private static int _currentCount;
protected override void OnInitialized()
{
plcData.PropertyChanged += OnIncrement;
}
private async void OnIncrement(object? sender, PropertyChangedEventArgs e)
{
_currentCount = plcData.Counter;
await InvokeAsync(() =>
{
StateHasChanged();
});
}
}
Also you need to add services to the container.
builder.Services.AddHostedService<PlcService>();
...
...
builder.Services.AddSingleton<PlcData>();
Here is a typical Program.cs from .net6 Blazor-Server And that's it!
UPDATE 3
Using System.Threading
#page "/"
#using System.Threading;
<h3>#_currentCount</h3>
#code {
private int _currentCount;
protected override void OnInitialized()
{
var timer = new Timer(new TimerCallback(_ =>
{
_currentCount++;
InvokeAsync(() =>
{
StateHasChanged();
});
}), null, 1000, 1000);
}
}
I don't see where you are calling the Read_CNC_Status() method. If you don't call it, then nothing you wrote about the timer is ever executed. You can call it in the component's OnAfterRender function (or in the constructor of your partial class).
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
Read_CNC_Status();
}
base.OnAfterRender(firstRender);
}
I ran the rest of the code and it works.
EDIT: It's not necessary, but I also recommend that you make sure the handler is not called again before the previous execution is finished (this could happen for example if Read_PLC_Data() is slow and takes more than the timer's Interval to complete). To do that, you can set the AutoReset property of your timer to false and manually restart the timer each time at the end of your handler, like this:
public async void Read_CNC_Status()
{
PLCService.Connect_PLC(); // Connection code is in a sourced service
Initialise_Timer1();
}
public void Initialise_Timer1()
{
timer1.Elapsed += new ElapsedEventHandler(OnTimedEvent1);
timer1.Interval = 1000;
timer1.AutoReset = false;
counter_CNC_status = 0;
timer1.Start();
}
public void OnTimedEvent1(object source, ElapsedEventArgs e)
{
try
{
PLCService.Read_PLC_Data();
counter_CNC_status += 1; // This counter is not counting !!!
}
finally
{
if(counter_CNC_status >= 30)
{
counter_CNC_status = 0;
timer1.Enabled = false;
}
else
{
timer1.Start();
}
}
StateHasChanged();
}
The best approach for me is to use QUARTZ.NET

How to unit test methods in windows form class?

I have a developing a c# windows form application and I have a method that exists inside the main form class.
Imagine methodA as part of the main form class.
public void methodA() {
A.someMethod();
B.someMethod();
// some more code
if (someCondition) {
// execute some code
}
// initialize timer and set event handler for timer
// run new thread
}
class A {
someMethod() {...}
}
class B {
someMethod() {...}
}
How would I run tests to test the branch logic of this methodA (isCondition)? since it involves initializing timer and running threads. Can i only verify the logic while doing system test ? I dont think it is possible to mock the timer and threading function.
Thank you !
Of course you can mock the timer. This is by creating a new interface, say, ITimerWrapper and implement it by using the concrete Timer class. Basically a wrapper of the Timer class. Then use that instead of the concrete Timer class you have.
Something in the tune of:
public partial class Form1 : Form
{
private readonly ITimerWrapper _timerWrapper;
public Form1(ITimerWrapper timerWrapper)
{
InitializeComponent();
this._timerWrapper = timerWrapper; // of course this is done via dependency injection
this._timerWrapper.Interval = 1000;
}
private void Form1_Load(object sender, EventArgs e)
{
// now you can mock this interface
this._timerWrapper.AddTickHandler(this.Tick_Event);
this._timerWrapper.Start();
}
private void Tick_Event(object sender, EventArgs e)
{
Console.WriteLine("tick tock");
}
}
public interface ITimerWrapper
{
void AddTickHandler(EventHandler eventHandler);
void Start();
void Stop();
int Interval { get; set; }
}
public class TimerWrapper : ITimerWrapper
{
private readonly Timer _timer;
public TimerWrapper()
{
this._timer = new Timer();
}
public int Interval
{
get
{
return this._timer.Interval;
}
set
{
this._timer.Interval = value;
}
}
public void AddTickHandler(EventHandler eventHandler)
{
this._timer.Tick += eventHandler;
}
public void Start()
{
this._timer.Start();
}
public void Stop()
{
this._timer.Stop();
}
}
Then for the spinning of a new thread, that's also testable by doing the same thing.
Bottomline is to have an interface to separate concerns and mock the interface on your unit test.

Update a form in parallel with an installation script?

I currently have an installation "framework" that does specific things. What I need now to do is be able to call my form in parallel with my script. Something like this:
InstallationForm f = new InstallationForm();
Application.Run(f);
InstallSoftware(f);
private static void InstallSoftware(InstallationForm f) {
f.WriteToTextbox("Starting installation...");
Utils.Execute(#"C:\temp\setup.msi", #"-s C:\temp\instructions.xml");
...
f.WriteToTextbox("Installation finished");
The current way I can do this is by adding the Form.Shown handler in InstallSoftware, but that seems really messy. Is there anyway I can do this better?
Your code will not work, because Application.Run(f) returns not until the form was closed.
You may use a simplified Model/View/Controller pattern. Create an InstallationFormController class that has several events, e.g. for textual notifications to be written to your textbox. The InstallationForm registers on these events in it's OnLoad() method and then calls InstallationFormController.Initialize(). That method starts your installation (on a worker thread/task). That installation callback method fires several text events.
InstallationForm f = new InstallationForm(new InstallationFormController());
Application.Run(f);
internal class InstallationFormController
{
public event EventHandler<DataEventArgsT<string>> NotificationTextChanged;
public InstallationFormController()
{
}
public void Initialize()
{
Task.Factory.StartNew(DoInstallation);
}
private void DoInstallation()
{
...
OnNotificationTextChanged(new DataEventArgsT<string>("Installation finished"));
}
private void OnNotificationTextChanged(DataEventArgsT<string> e)
{
if(NotificationTextChanged != null)
NotificationTextChanged(this, e);
}
}
public class DataEventArgsT<T> : EventArgs
{
...
public T Data { get; set; }
}
internal class InstallationForm : Form
{
private readonly InstallationFormController _controller;
public InstallationForm()
{
InitializeComponent();
}
public InstallationForm(InstallationFormController controller) : this()
{
if(controller == null)
throw new ArgumentNullException("controller")
_controller = controller;
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
_controller.NotificationTextChanged += Controller_NotificationTextChanged;
_controller.Initialize();
}
protected virtual void Controller_NotificationTextChanged(object sender, DataEventArgsT<string> e)
{
if(this.InvokeRequired)
{ // call this method on UI thread!!!
var callback = new EventHandler<DataEventArgsT<string>>(Controller_NotificationTextChanged);
this.Invoke(callback, new object[] {sender, e});
}
else
{
_myTextBox.Text = e.Data;
}
}
...
}

Cancellation async in IDisposable class

In my project I use a manager to control a plugin. The main idea is that this plugin must work only in single thread in multythreads WPF application. There is only one instance of plugin in PluginController.
So when I call Start method: it stops plugin (if running) and start it with new argument. Few times a second plugin notificate caller about it's state, and ViewModel shows it in the WPF window.
When I call method Start some times one after one, i see that the previous instance of ViewModel is not destroyed, but only sleeps after Stop. And it calls Update method as god as a new one instance. So my interface twiches becouse two instances are updating it's state. In log is see alternately lines from first one and second one.
But when I call Start(...) then Stop() and then Start(...) again everything works fine.
So
SomeManager.Start(...);
SomeManager.Start(...);
works with errors. And
SomeManager.Start(...);
SomeManager.Stop();
SomeManager.Start(...);
works fine. Can anybody explain me my mistake?
Down lied simplified code.
public static SomeManager
{
public static void Start(SomeArg arg)
{
Stop(); // forgotten code
var vm = GetMainPageVM();
vm.SomeVM = new SomeViewModel(arg);
vm.SomeVM.StartCommand.Execute(null);
}
public static void Stop()
{
var vm = GetMainPageVM();
if (vm.SomeVM != null)
{
vm.SomeVM.Stop();
vm.SomeVM.Dispose();
vm.SomeVM = null;
}
}
}
public sealed SomeViewModel : ViewModelBase, IDisposable
{
private readonly Guid _guid = Guid.NewGuid();
private IPlugin _plugin;
private SomeArg _arg;
public ICommand StartCommand {get; }
public CancellationTokenSource Source {get; }
public SomeViewModel(SomeArg arg)
{
this._arg = arg;
this._plugin = PluginController.GetPluginByName("SomePlugin");
StartCommand = new RelayCommand(StartAsync);
}
~SomeViewModel()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{ ... }
private async Task StartAsync()
{
var progress = new Progress<ISomeProgress>(Update);
try
{
await StartImplementationAsync(progress).ConfigureAwait(false);
}
catch (Exception e) { ... }
}
private async Task StartImplementationAsync(Progress<ISomeProgress> progress)
{
var result = await this._plugin.startAsync(
this._arg,
progress,
this.Source.Token
).ConfigureAwait(false);
}
public void Stop()
{
this._plugin.Stop();
}
private void Update() {log.Debug($"this._guid" ....); }
}
public sealed SomePlugin: IPlugin
{
public async Task<SomeResult> StartAsync(SomeArg args, IProgress<SomeProgress>, CancellationToken cancellationToken)
{ ... }
public void Stop() { ... }
}
UPDATE: I think the problem in simple words is : how to correctly cancel async operation in IDisposable object in normal case with CancellationTokenSource.Cancel() and in unnormal case when Dispose() or Finalizer is called

Custom event and invocation on main thread

I was given a generic API class, that contains a custom event which always needs to be invoked by the main UI thread.
My job is to banish these invocation call from the custom class, to make it "painless".
It should be synchronized like the default events in WinForms (eg the Timer "Elapsed" event, which also needs no invocation when it published values to a text box)
Is it possible to solve this, since the custom class needs to know where to invoke?
Here's the (important part of the) code:
public class ContactSensorHelper
{
public event OnReleaseStateChanged ReleaseStateChanged;
public delegate void OnReleaseStateChanged(ContactSensorEventArgs e);
private ContactSensorEventArgs.ReleaseState recentReleaseState;
public void ReportStateChanged()
{
if (ReleaseStateChanged != null)
ReleaseStateChanged(new ContactSensorEventArgs()
{
State = recentReleaseState
});
}
public class ContactSensorEventArgs : EventArgs
{
//......
public ReleaseState State { get; set; }
//......
public enum ReleaseState
{
FullReleased,
PartlyReleased,
NotReleased
}
}
}
The call from main UI:
public void SensorInit()
{
//....
sensorHelper.ReleaseStateChanged += releaseStateChanged;
//....
}
private void releaseStateChanged(ContactSensorEventArgs e)
{
//example
textBox1.Text = e.State.ToString(); // Thread exception (obviously)
}
Does anybody have me a hint to start?
You could do this by using your own event calling, and storing a reference to the thread, when the event is attached.
With the event add/remove syntax, you can have the caller attach to the event like before, but internally you store a list, with a reference to the thread (using an AsyncOperation) and the delegate to be called (used a Tuple containing both in the example)
Below is an example. I tested it, and it worked as expected when testing, but you might have to add some locking of the list to make it thread safe in case events are added/removed simultaneously.
public class ContactSensorHelper:IDisposable
{
public delegate void OnReleaseStateChanged(ContactSensorEventArgs e);
private ContactSensorEventArgs.ReleaseState recentReleaseState;
public void ReportStateChanged()
{
if (statechangedList.Count > 0)
{
var e = new ContactSensorEventArgs()
{
State = recentReleaseState
};
statechangedList.ForEach(t =>
t.Item1.Post(o => t.Item2((ContactSensorEventArgs)o), e));
}
}
List<Tuple<AsyncOperation, OnReleaseStateChanged>> statechangedList = new List<Tuple<AsyncOperation,OnReleaseStateChanged>>();
public event OnReleaseStateChanged ReleaseStateChanged
{
add
{
var op = AsyncOperationManager.CreateOperation(null);
statechangedList.Add(Tuple.Create(op, value));
}
remove
{
var toremove = statechangedList.Where(t => t.Item2 == value).ToArray();
foreach (var t in toremove)
{
t.Item1.OperationCompleted();
statechangedList.Remove(t);
}
}
}
public void Dispose()
{
statechangedList.ForEach(t => t.Item1.OperationCompleted());
statechangedList.Clear();
}
public class ContactSensorEventArgs : EventArgs
{
//......
public ReleaseState State { get; set; }
//......
public enum ReleaseState
{
FullReleased,
PartlyReleased,
NotReleased
}
}
}

Categories

Resources