Akka.NET actors typically run in their own (non-UI) threads. This means by default accessing UI objects (for example setting some text in a text box) will cause a thread access exception.
For example the following code shows an actor trying to set the text of the button. The actor is not running in the UI context but the Button is so we’re trying to cross threads which causes the exception shown below.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Button Click="Button_OnClick">Say Hi</Button>
<TextBox Name="GreetingTextBox"></TextBox>
</StackPanel>
</Window>
public class HelloWorldActor : ReceiveActor
{
public HelloWorldActor(TextBox textBox)
{
Receive<string>(s => textBox.Text = s);
}
}
public partial class MainWindow : Window
{
private ActorSystem _actorSystem;
private IActorRef _actor;
public MainWindow()
{
InitializeComponent();
_actorSystem = ActorSystem.Create("MySystem");
_actor = _actorSystem.ActorOf(Props.Create(() => new HelloWorldActor(GreetingTextBox)));
}
private void Button_OnClick(object sender, RoutedEventArgs e)
{
_actor.Tell("G'day");
}
}
The thread on which an actor runs can be configured, either in code or in the app/web HOCON config.
We can modify the actor props in code as follows:
_actor = _actorSystem.ActorOf(Props.Create(() => new HelloWorldActor(GreetingTextBox))
.WithDispatcher("akka.actor.synchronized-dispatcher"));
Now the HelloWorldActor will run on the UI thread and be able to access the text box as the following screenshot shows:
MVVM and INotifyPropertyChanged
When using MVVM and a view-model, rather that passing the UI element to the actor, the view-model can instead be passed. The actor now updates the view-model which the textbox is databound to. In this situation the data-binding mechanism/INotifyPropertyChanged will take care of marshalling changes to the UI thread. This means that the actor no longer needs to be configured to run on the UI thread. The following code shows these changes:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Button Click="Button_OnClick">Say Hi</Button>
<TextBox Text="{Binding Greeting}"></TextBox>
</StackPanel>
</Window>
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using Akka.Actor;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
private ActorSystem _actorSystem;
private IActorRef _actor;
private ViewModel _viewModel ;
public MainWindow()
{
InitializeComponent();
_viewModel = new ViewModel();
_actorSystem = ActorSystem.Create("MySystem");
_actor = _actorSystem.ActorOf(Props.Create(() => new HelloWorldActor(_viewModel)));
DataContext = _viewModel;
}
private void Button_OnClick(object sender, RoutedEventArgs e)
{
_actor.Tell("G'day");
}
}
public class HelloWorldActor : ReceiveActor
{
private readonly ViewModel _viewModel;
public HelloWorldActor(ViewModel viewModel)
{
_viewModel = viewModel;
Receive<string>(s => _viewModel.Greeting = s);
}
}
public class ViewModel : INotifyPropertyChanged
{
private string _greeting;
public string Greeting
{
get { return _greeting; }
set
{
_greeting = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
You can read more about dispatchers in the Akka.NET docs and learn more about using Akka.NET with WPF in my Pluralsight course: Building Reactive Concurrent WPF Applications with Akka.NET.
You can start watching with a Pluralsight free trial.
SHARE: