Update Collection with multithreading and WPF - c#

At the moment I have a class for scanning the network:
public class Network {
public event NewDeviceHandler NewDevice;
public event ScanFinishedHandler ScanFinished;
//...
public void Scan() { /* ... */ }
}
I want to update my UI as soon as a new Device was found.
What is the best practice in this case? Should I use events or is it better to use something like an ObservableCollection?
And I have to call this method in my UI-Thread (WPF). How should I do that?
Create a new Task in my UI-Application
Create a new Task in the Scan-Method
Use asnyc / await
Thank you very much for your help.

If you want to update your UI as soon as a new Device was found, you will only need ObservableCollection and Task. For example:
In Network class:
public event NewDeviceHandler<Device> NewDevice;
public void StartScan()
{
Task.Run(() => Scan() );
}
In view-model:
public ObservableCollection<Device> DevicesCollection { get; set; }
In code-behind:
private Network networkService = new Network();
...
// Somewhere in initialization code:
networkService.NewDevice += (sender, device) => Dispatcher.Invoke(() => viewModel.DevicesCollection.Add(device) );
...
private void ScanButton_OnClick(object sender, RoutedEventArgs e)
{
viewModel.DevicesCollection.Clear();
networkService.StartScan();
}

Related

How to raise event, in a net6 lib, that will be catch by the Dispatcher Thread when there is one and any thread when there is no Dispatcher [duplicate]

I have a class which plays some music like this. It also saves the GUI thread id in a private int during construction:
public class MediaPlayer {
public event EventHandler<Track> OnTrackComplete;
private int GuiThreadId;
public MediaPlayer(...){
...
this.GuiThreadId = Thread.CurrentThread.ManagedThreadId;
}
public void Play(){
Task t = Task.Factory.StartNew(() =>
{
//On Song complete
FireOnTrackComplete();
});
}
protected virtual void FireOnTrackComplete()
{
if (OnTrackComplete != null)
OnTrackComplete(this, loadedTrack);
}
}
Is it possible to call FireOnTrackComplete() on a Thread with a specific ID. In this case, the ID is stored in the this.GuiThreadId?
Most of the solutions I have come across suggest I use invokes in my GUI code in methods which listen to the OnTrackComplete event handler. I want to avoid doing this. I want to do everything in the MediaPlayer class
Based on the accepted answer bellow this is how I changed my code
public class MediaPlayer {
public event EventHandler<Track> OnTrackComplete;
private SynchronizationContext callerCtx;
public MediaPlayer(...){
...
callerCtx = System.Threading.SynchronizationContext.Current;
}
public void Play(){
Task t = Task.Factory.StartNew(() =>
{
//On Song complete
FireOnTrackComplete();
});
}
protected virtual void FireOnTrackComplete()
{
Action e = () =>
{
if (OnTrackComplete != null)
OnTrackComplete(this, loadedTrack);
};
FireEvent(e);
}
//... Other events ... //
protected virtual void FireEvent(Action e)
{
if (callerCtx == null)
e();
else
callerCtx.Post(new SendOrPostCallback((_) => e()), null);
}
}
The SynchronizationContext class was meant to solve this problem. Copy the value of its Current property in the constructor, use its Post() or Send() method later. This ensures your library will work with any GUI class library. Like this:
class MediaPlayer {
public MediaPlayer() {
callersCtx = System.Threading.SynchronizationContext.Current;
//...
}
private void FireOnTrackComplete() {
if (callersCtx == null) FireOnTrackCompleteImpl();
else callersCtx.Post(new System.Threading.SendOrPostCallback((_) => FireOnTrackCompleteImpl()), null);
}
protected virtual void FireOnTrackCompleteImpl() {
var handler = OnTrackComplete;
if (handler != null) handler(this, loadedTrack);
}
private System.Threading.SynchronizationContext callersCtx;
}
Pass a reference to the main dispatcher (=GUI-Thread's dispatcher) and call Invoke on it directly with your callback code.
public class MediaPlayer {
public event EventHandler<Track> OnTrackComplete;
private Dispatcher { get; set; }
public MediaPlayer(Dispatcher guiDispatcher){
// Other code ...
if(guiDispatcher == null)
throw new ArgumentNullException("guiDispatcher", "Cannot properly initialize media player, since no callback can be fired on GUI thread.");
Dispatcher = guiDispatcher;
}
public void Play() {
// Fire immediately on thread calling 'Play', since we'll forward exec. on gui thread anyway.
FireOnTrackComplete();
}
protected virtual void FireOnTrackComplete()
{
// Pretending "loadedTrack" was set somewhere before.
Dispatcher.Invoke(() => {
if (OnTrackComplete != null)
OnTrackComplete(this, loadedTrack);
});
}
}
// Somewhere in your initialization code
// ...
MediaPlayer player = new MediaPlayer(App.Current.Dispatcher); // If you use WPF. Don't know if this applies to WinForms too.
// ...
To be able to execute code on another thread, you must have a queue or message pump waiting for a new item to process.
This is already done in winforms and wpf via Control.Invoke and IDispatcher.Invoke. If you really want to avoid having the Control perform the listening, you'll have to pass the control into the MediaPlayer. It's really awkward, but there's a big complaint on SO that the first answer is "how about you stop doing that thing you're trying to do".. so here goes:
public class MediaPlayer {
public event EventHandler<Track> OnTrackComplete;
private int GuiThreadId;
private readonly Control control;
public MediaPlayer(..., Control control){
...
this.GuiThreadId = Thread.CurrentThread.ManagedThreadId;
this.contrl = control;
}
public void Play(){
Task t = Task.Factory.StartNew(() =>
{
//On Song complete
FireOnTrackComplete();
});
}
protected virtual void FireOnTrackComplete()
{
var trackComplete = OnTrackComplete;
if (onTrackComplete != null)
this.control.Invoke((MethodInvoker) delegate {trackComplete(this, loadedTrack);});
}
}
Apologies if there's a typo, I don't have everything in front of me to verify with; but this should get you what you're after.

WPF MVVM Async event invoke

I am lost in this one, i want my Viewmodel to use a event delegate so i can subscribe to it, open some dialog and wait for the dialog result. Later the ViewModel should do whatever it wants with the dialog result.
Here is how i implemented it (resumed code):
public class MyViewModel()
{
public delegate TributaryDocument SearchDocumentEventHandler();
public event SearchDocumentEventHandler SearchDocument;
//Command for the search button
public CommandRelay SearchDocumentCommand { get; set; }
//Document that i found in the dialog.
public TributaryDocument Document { get; set; }
public MyViewModel()
{
SearchDocumentCommand = new CommandRelay(DoSearchDocument);
}
//The command execution
public void DoSearchDocument()
{
//Event used here !
Document = SearchDocument?.Invoke();
}
}
public class MyUIControl : UserControl
{
public MainWindow MainWindow { get; }
public MyUIControl()
{
MainWindow = Application.Current.Windows[0] as MainWindow;
DataContextChanged += MyUIControl_DataContextChanged;
}
private void MyUIControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var modelView = (MyViewModel)DataContext;
modelView.SearchDocument += MyUIControl_SearchDocument;
}
private TributaryDocument MyUIControl_SearchDocument()
{
//Dont know what to do here... i am lost on this part.
return await MainWindow.ShowDialog(new MyDocumentSearcherDialog());
}
}
//The signature for MainWindow.ShowDialog
public async Task<object> ShowDialog(object dialog)
{
return await DialogHost.Show(dialog, "MainDialog");
}
MyDocumentSearcherDialog is just a dialog where i search and return a TributaryDocument object.
The problem to my understanding comes from this part (since i cant compile it):
private TributaryDocument MyUIControl_SearchDocument()
{
return await MainWindow.ShowDialog(new MyDocumentSearcherDialog());
}
I cant use await without changing the method signature to async. If i change it to async then i must return a Task<TributaryDocument> and change the event delegate:
public delegate Task<TributaryDocument> SearchDocumentEventHandler();
//On MyUIControl
private Task<TributaryDocument> MyUIControl_SearchDocument()
{
return await MainWindow.ShowDialog(new MyDocumentSearcherDialog());
}
//On MyViewModel
public async void DoSearchDocument()
{
//Event used here !
Document = await Task.Run(async () => await SearchDocument?.Invoke());
}
If i do this i get the following exception:
Additional information: The calling thread must be STA, because many
UI components require this.
It seems like all you need to do is to remove the Task.Run (there is no need to Offload to another thread in this situation). The Task.Run will definitely give you a STA Thread Exception if you are doing UI work from within.
However, in short the Async and Await Pattern will create a continuation with the current SynchronisationContext, so there is no need to worry about it.
public async void DoSearchDocument()
{
await SearchDocument?.Invoke();
}
Note : Since this is an event, it's about the only place it's OK to use async void.

Update UI from another GPIOListener class

I'm developing an application with windows-10-t platform on raspberry-pi3. The application has several pages and listens GPIO ports asyncrhonously in the background. It collects data from GPIO and sends to the WCF-Service, after a bit the UI should be updated by the data coming from the WCFService. I've also tried using Tasks, Dispatcher.Invoke etc. but nothing worked properly. I can collect data coming from GPIO but cannot update UI. What am I doing wrong?
Here is the background GPIO listener class with static variables (I'm listening GPIO in other pages too.):
public sealed class GPIO{
private static MainPage mainpage;
public static event EventHandler ProgressUpdate;
public static void InitGPIO(MainPage sender)
{
mainpage = sender;
DataPin.DebounceTimeout = TimeSpan.FromMilliseconds(50);
DataPin.ValueChanged += DataPin_ValueChanged;
}
public static void DataPin_ValueChanged(GpioPin sender, GpioPinValueChangedEventArgs e)
{
if (e.Edge == GpioPinEdge.FallingEdge)
{
Task.Run(() => AddData(0));
}
}
public static async void AddData(int prm_Data)
{
// WCF-Service Operation
await Service.wsClient.GPIOValueAddition(prm_Data);
GPIO.ProgressUpdateOperation();
}
private static void ProgressUpdateOperation()
{
mainpage.GPIO_ProgressUpdate(typeof(GPIO), new EventArgs());
}
}
And here is the page that contains the UI to be updated:
public sealed partial class MainPage : Page
{
public MainPage()
{
GPIO.InitGPIO(this);
GPIO.ProgressUpdate += GPIO_ProgressUpdate;
}
public void GPIO_ProgressUpdate(object sender, EventArgs e)
{
// WCF-Service Operation
service_data = (int)Service.wsClient.GetDataFromServicetoUpdateUI(parameter).Result;
// UI-update
txtUpdate.Text = service_data.ToString();
}
}
EDIT: I forgot to add the exception. "The application called an interface that was marshalled for a different thread. (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))" exception is thrown at AddData function called in DataPin_Valuechanged.
I found the solution in here : https://stackoverflow.com/a/27698035/1093584
Here is the new update-UI function :
public void GPIO_ProgressUpdate(object sender, EventArgs e)
{
await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
{
service_data = await Service.wsClient.GetDataFromServicetoUpdateUI(parameter);
// UI-update
txtUpdate.Text = service_data.ToString();
});
}

push work to observers

I have a listener, which receives work in the form of IPayload. The listener should push this work to observers who actually do the work. This is my first crude attempt to achieve this:
public interface IObserver
{
void DoWork(IPayload payload);
}
public interface IObservable
{
void RegisterObserver(IObserver observer);
void RemoveObserver(IObserver observer);
void NotifyObservers(IPayload payload);
}
public class Observer : IObserver
{
public void DoWork(IPayload payload)
{
// do some heavy lifting
}
}
public class Listener : IObservable
{
private readonly List<IObserver> _observers = new List<IObserver>();
public void PushIncomingPayLoad(IPayload payload)
{
NotifyObservers(payload);
}
public void RegisterObserver(IObserver observer)
{
_observers.Add(observer);
}
public void RemoveObserver(IObserver observer)
{
_observers.Remove(observer);
}
public void NotifyObservers(IPayload payload)
{
Parallel.ForEach(_observers, observer =>
{
observer.DoWork(payload);
});
}
}
Is this a valid approach that follows the observer/observable pattern (i.e. pub sub?)? My understanding is that the NotifyObservers also spins up a threat for each payload. Is this correct? Any improvement suggestions very much welcome.
Please note that all observers have to finish their work before new work in the form of a payload is passed on to them - the order of 'observation' does not matter. Basically, the listener has to act like a master whilst exploiting the cores of the host as much as possibly using the TPL. IMHO this requires the explicit registration of observers with the listener/Observable.
PS:
I think Parallel.ForEach does not create a thread for each observer: Why isn't Parallel.ForEach running multiple threads? If this is true how can I ensure to create a thread for each observer?
An alternative I have in mind is this:
public async void NotifyObservers(IPayload payload)
{
foreach (var observer in _observers)
{
var observer1 = observer;
await Task.Run(() => observer1.DoWork(payload));
}
await Task.WhenAll();
}
Of course you can do it this way, but in .net that is not needed if you dont want to reinvent the wheel :-)
In c# there this could be done using events.
A short example :
//Your Listener who has a public eventhandler where others can add them as listeners
public class Listener{
//your eventhandler where others "add" them as listeners
public event EventHandler<PayLoadEventsArgs> IncomingPayload;
//a method where you process new data and want to notify the others
public void PushIncomingPayLoad(IPayload payload)
{
//check if there are any listeners
if(IncomingPayload != null)
//if so, then notify them with the data in the PayloadEventArgs
IncomingPayload(this, new PayloadEventArgs(payload));
}
}
//Your EventArgs class to hold the data
public class PayloadEventArgs : EventArgs{
Payload payload { get; private set; }
public PayloadEventArgs(Payload payload){
this.payload = payload;
}
}
public class Worker{
//add this instance as a observer
YourListenerInstance.IncomingPayload += DoWork;
//remove this instance
YourListenerInstance.IncomingPayload -= DoWork;
//This method gets called when the Listener notifies the IncomingPayload listeners
void DoWork(Object sender, PayloadEventArgs e){
Console.WriteLine(e.payload);
}
}
EDIT: As the question asks for parallel execution, how about doing the new thread at the subscriber side? I think this is the easiest approach to achieve this.
//Inside the DoWork method of the subscriber start a new thread
Task.Factory.StartNew( () =>
{
//Do your work here
});
//If you want to make sure that a new thread is used for the task, then add the TaskCreationOptions.LongRunning parameter
Task.Factory.StartNew( () =>
{
//Do your work here
}, TaskCreationOptions.LongRunning);
Hopefully this answers your question? If not, please leave a comment.

Adlink PCI-7250 eventcallback

I just wrote a simple C# to get an eventcallback from PCI-7250 (Data Acquisition Card) when any of the digital inputs go high. Here is my code:
public delegate void ReadDelegate(uint value)
public void Form1_Load(object sender, EventArgs e)
{
m_dev = DASK.Register_Card(DASK.PCI_7250,0);
ReadDelegate ReadNow = new ReadDelegate(FunctionToCall)
DASK.DI_EventCallBack((ushort)m_dev,1,(short)DASK.DBEvent,ReadNow)
}
private void FunctionToCall(uint int_value)
{
MessageBox.Show(int_value)
}
When run it just keep"s up throwing some random numbers during runtime and then finally crashes. I believe it has something to do with the EventType (DASK.DBEvent). I went through the manual but nothing more is mentioned about the DASK.DBEvent.
Kindly please advise.
Since the device doesn't have support for callbacks in its driver, you could adapt the driver's API from synchronous calls to callbacks by polling the device in a background thread.
First, create a class that polls the device for the physical event you're interested in responding to. Then, in your GUI code, put the polling work in the background and respond to the callback in the main thread.
I'm not familiar with the ADLink driver, so I'll just summarize a design approach and sketch some pseudocode that isn't threadsafe. This is a naive approach to using Tasks, but you could also update it to use continuations or async/await. Or, if you need more than one responder, make the callback raise an event that your other classes can subscribe to.
Polling class
public class EdgeDetector
{
public delegate void OnRisingEdgeDetected(uint currentValue, uint linesThatAsserted);
private bool m_shouldPoll;
private void PollForRisingEdge(PCI_7250 device, OnRisingEdgeDetected onRisingEdgeDetected)
{
while (m_shouldPoll)
{
// Optional: sleep to avoid consuming CPU
uint newPortValue = device.ReadAllDigitalLines();
uint changedLines = m_currentPortValue ^ newPortValue;
uint risingEdges = newPortValue & changedLines;
m_currentPortValue = newPortValue;
if (risingEdges != 0)
{
onRisingEdgeDetected(currentValue: newPortValue,
linesThatAsserted: risingEdges);
}
}
public void Start(PCI_7250 device, OnRisingEdgeDetected onRisingEdgeDetected)
{
m_shouldPoll = true;
PollForRisingEdge(device, onRisingEdgeDetected);
}
public void Stop()
{
m_shouldPoll = false;
}
}
WinForm class
private void Initialize()
{
m_dev = DASK.Register_Card(DASK.PCI_7250, 0);
m_mainThreadScheduler = TaskScheduler.FromCurrentSynchronizationContext();
}
private void StartEdgeDetection()
{
m_edgeDetectionTask = Task.Factory.StartNew( () =>
{
m_edgeDetector.Start(device: m_dev, onRisingEdgeDetected: RescheduleOnMainThread);
});
}
private RescheduleOnMainThread(uint currentValue, uint linesThatAsserted)
{
m_onEdgeDetectionTask = Task.Factory.StartNew(
action: () =>
{
MessageBox.Show(currentValue);
},
cancellationToken: null,
creationOptions: TaskCreationOptions.None,
scheduler: m_mainThreadScheduler);
}
private void CleanUp()
{
m_edgeDetector.Stop();
m_edgeDetectionTask.Wait();
m_onEdgeDetectionTask.Wait();
}
public void Form1_Load(object sender, EventArgs e)
{
Initialize();
StartEdgeDetection();
}
public void Form1_Closed(object sender, EventArgs e)
{
CleanUp();
}

Categories

Resources