How to call sibling objects from a foreign thread - c#

I have a WPF application with lots of user controls. One of these controls also uses a 3rd party DLL that watches an external system and produces events. I subscribe to those events and handle them with something like this:
public class ControlClassD
{
private 3rdPartyEventSource _3rdPartyEventSource = new 3rdPartyEventSource();
public ControlClassD()
{
_3rdPartyEventSource.NewEvent += _3rdPartyEventSource_NewEvent;
_3rdPartyEventSource.StartMakingEventsWhenSomethingHappens();
}
private void _3rdPartyEventSource_NewEvent(object o)
{
InstanceOfControlClassA.doSomethingWith(o);
InstanceOfControlClassB.doSomethingWith(o);
InstanceOfControlClassC.doSomethingWith(o);
}
}
All of the InstanceOfControlClassx were instantiated by whatever thread runs the _Loaded event handler in the MainWindow class at startup.
The thread executing the handler is one created by the 3rdPartyEventSource and has no access to all these things (as demonstrated by error messages of that nature)
What I would like to do is let the thread delivered by the 3rdPartyEventSource go back and have HandleNewEvent executed by the thread that created all of those instances (CreatorThread). Like:
private void _3rdPartyEventSource_NewEvent(object o)
{
SomehowInvokeCreatorThread(new Action(() => HandleNewEvent(o)));
}
private void HandleNewEvent(object o)
{
InstanceOfControlClassA.doSomethingWith(o);
InstanceOfControlClassB.doSomethingWith(o);
InstanceOfControlClassC.doSomethingWith(o); //which may access this
}
How can I do that?

There are many ways of doing this, but the following one might be the simplest. Just create your own wrapper event which will be fired on the UI thread:
public class ControlClassD
{
public class WrapperEventArgs: EventArgs { public object Arg { get; set; } }
public event EventHandler<WrapperEventArgs> WrapperEvent = delegate { };
private 3rdPartyEventSource _3rdPartyEventSource = new 3rdPartyEventSource();
public ControlClassD()
{
var dispatcher = Dispatcher.CurrentDispatcher;
_3rdPartyEventSource.NewEvent += obj =>
dispatcher.BeginInvoke(new Action(() =>
this.WrapperEvent(this, new WrapperEventArgs { Arg = obj })));
this.WrapperEvent += ControlClassD_WrapperEventHandler;
_3rdPartyEventSource.StartMakingEventsWhenSomethingHappens();
}
private void ControlClassD_WrapperEventHandler(
object sender, WrapperEventArgs args)
{
InstanceOfControlClassA.doSomethingWith(args.Arg);
InstanceOfControlClassB.doSomethingWith(args.Arg);
InstanceOfControlClassC.doSomethingWith(args.Arg);
}
}

Related

Keep WPF UI responsive - Communication between classes

In our WPF application, we have classes that need to "raise" a notification that something has happened, and other classes that operate as listeners to that notification and have to execute some code as a response upon getting that notification. This is the common pattern of Publish/Subscribe.
Now, I will show you how it is done in our project. I think we use a bad practice of that pattern, and this causes our UI to freeze and not be responsive as expected.
The event args class:
public class IsDisconnectedAction : EventArgs
{
public override string ToString()
{
return GetType() + " Key: ";
}
public ElementType ElementKey { get; set; }
public static ActionTemplate<IsDisconnectedAction> MY_Action = new ActionTemplate<IsDisconnectedAction>();
}
Raising the Notification
IsDisconnectedAction.MY_Action.Raise(new IsDisconnectedAction() { ElementKey = _elementKey });
The implementation of the Raise method
public void Raise(T info)
{
if (_event == null) return;
InvokeIfNecessary.Invoke(() =>
{
_event (null, info);
});
}
private event EventHandler<T> _event;
public event EventHandler<T> Event
{
add
{
lock (_lock)
{
_event += value;
}
}
remove
{
lock (_lock)
{
_event -= value;
}
}
}
private readonly object _lock = new object();
The Invoke method of InvokeIfNecessary
public static void Invoke(Action action)
{
if (Application.Current == null)
{
action();
return;
}
if (Application.Current.Dispatcher.CheckAccess())
action();
else
{
Application.Current.Dispatcher.Invoke(action, DispatcherPriority.Send);
}
}
NOTE this code is executed on the UI thread, and I think it's not the correct approach.
Then, the listener:
It can be a class in the UI that indeed need to update something in the UI:
IsDisconnectedAction.MY_Action.Event += OnElementDisconnected;
private void OnElementDisconnected(object sender, IsDisconnectedAction e)
{
if (e == null) return;
_textBlock.Text = "Disconnected";
}
It can be a class that listens to the events in the same manner but does NOT even need to update anything in the UI (Here I think that raising the event and listening to it, can all be done outside of the UI thread).
Can you please suggest what is the best practice to achieve what I want?
Sometimes these events are raised so many times per second and all of that happens in the UI thread, and I think it might harm the responsiveness of the UI. Thank!

Subscribing to an event of a class that holds the override method of the class you reference

I am working with background workers to update a progress bar in a WPF UI I am working on. This background worker is getting its progress updates from multiple events that I am subscribed to, because the progress bar goes through several loading stages, and the percentages for those come from several places. here is some example/pseudo code explaining what I mean
The DoWork method of my background worker and the methods I am using to currently get some progress updates
// These are working fine
private void BwOnDoWork(object sender, DoWorkEventArgs doWorkEventArgs)
{
orderProcessing.OnOrderProgress += OrderStatus;
orderProcessing.OnStandardOrderProgress += StandardOrderStatus;
orderProcessing.CreateOrders(orders);
}
private void OrderStatus(int currentCount, int totalItems, string Message)
{
if (totalItems > 0)
bw.ReportProgress(Convert.ToInt32(((double)currentCount / (double)totalItems) * 100),
Message);
}
private void StandardOrderStatus(int currentCount, int totalItems, string Message)
{
if (totalItems > 0)
bw.ReportProgress(Convert.ToInt32(((double)currentCount / (double)totalItems) * 100),
Message);
}
Some code from my order processing class
public abstract class OrderProcessing
{
public delegate void OrderProgress(int CurrentItems, int TotalItems, string Message);
public event MasterSalesOrder.StandardOrderProgress OnStandardOrderProgress;
public event OrderProgress OnOrderProgress;
public abstract List<MasterSalesOrder> CreateOrders(List<Order> orders);
}
Some code from the class that holds the override method for CreateOrders()
public abstract class OrderProcessingFile : OrderProcessing
{
public event OrderProgress OnOrderProgress;
public override List<MasterSalesOrder> CreateOrders(List<Order> orders)
{
//Does Some Stuff
foreach(var stuff in stuffs)
{
OnOrderProgress(currentCount, totalCount, "Message");
}
}
}
Since I am clearly not explaining this well, I need to get info from the OrderProcessingFiles OnOrderProgress event via the OrderProcessing class that I create in the DoWork method.I am unsure on how to subscribe to an event when my code never directly instantiates an instance of the OrderProcessingFile class and it is never directly referred to.
I have tried looking for answers but as my title will show I am having a hard time even wording this in a way to get useful results, and I am genuinely stuck on this one. Let me know if more detail is needed, I tried to strip down my code to only the relevant parts but I feel like I'm explaining this strangely.
I would recommend that you create a thread safe singleton progress manager. Then have each of the background workers contact it with updates. The progress manager will use a DispatcherTimer (which runs on the GUI thread) to update the GUI appropriately.
Raw example:
public static class StatusReportManager
{
// Standard singleton code to create the manager and access it.
// Start/create the dispatch time as well.
private static DispatcherTimer Timer { get; set; }
private static object _syncObject = new object();
public static void ReportStatus(...)
{
lock (_syncObject)
{
// Process any states and set instance properties for reading
// by the timer operation.
}
}
private void ShowStatus() // Used by the dispatch timer
{
lock (_syncObject)
{
// Do any updates to the GUI in here from current state.
}
}
}
I have realized what it is I was really trying to do and have thus found an answer. Using the method found in this MSDN article I have implemented the follow code:
This is my UI
private void BwOnDoWork(object sender, DoWorkEventArgs doWorkEventArgs)
{
orderProcessing.OnOrderProgress += OrderStatus;
orderProcessing.CreateOrders(FanGlobal.BrandItems, FanGlobal.BrandItemMasterCustomers);
}
private void OrderStatus(object obj, OrderProcessing.OrderProgressEventArgs e)
{
if (e.totalCount > 0)
bw.ReportProgress(Convert.ToInt32(((double)e.currentCount / (double)e.totalCount) * 100),e.message);
}
This in my OrderProcessing class
public event EventHandler<OrderProgressEventArgs> OnOrderProgress;
public class OrderProgressEventArgs : EventArgs
{
public int currentCount;
public int totalCount;
public string message;
public OrderProgressEventArgs(int c, int t, string m)
{
currentCount = c;
totalCount = t;
message = m;
}
}
protected virtual void OnOrderProgressChanged(OrderProgressEventArgs e)
{
EventHandler<OrderProgressEventArgs> handler = OnOrderProgress;
if (handler != null)
{
handler(this, e);
}
}
public abstract List<MasterSalesOrder> CreateOrders(List<BrandItem> BrandItems = null, List<BrandItemMasterCustomer> BrandItemMasterCustomers = null);
and then I can use it in my child class OrderProcessingFile like so
public override List<MasterSalesOrder> CreateOrders(List<BrandItem> BrandItems = null, List<BrandItemMasterCustomer> BrandItemMasterCustomers = null)
{
//Do some Stuff
OnOrderProgressChanged(new OrderProgressEventArgs(count, totalItems, "Extracting"));
}
and everything is working like a charm. Sorry for the utterly confusing question and the apparent huge gap of knowledge I have/had, but hopefully this will help someone else in the future.

WPF - Threads and change variable

I have application with 1 simple thread:
public partial class MainWindow : Window
{
Thread historyThread = null;
Window historyWindow = null;
public MainWindow()
{
//...
}
#region EVENTS Magazyn
private void btn_K_FinalizujZakup_Click(object sender, RoutedEventArgs e)
{
if (dg_Klient.Items.Count > 0)
{
//Problem with 'Finalizuj' Method
new Historia.Wpis(MainWindowViewModel.klient).Finalizuj(viewModel.historiaWindow_ViewModel.historiaZakupow);
MainWindowViewModel.klient = new Klient.Klient();
dg_Klient.ItemsSource = MainWindowViewModel.klient;
}
}
#region History Thread
private void btn_K_HistoriaOpen_Click(object sender, RoutedEventArgs e)
{
//if(historyThread != null){
// historyThread.Abort();
// historyThread = null;
//}
historyThread = new Thread(new ThreadStart(History_ThreadStart));
historyThread.SetApartmentState(ApartmentState.STA);
historyThread.IsBackground = true;
historyThread.Start();
}
private void History_ThreadStart()
{
historyWindow = new HistoryWindow(viewModel.historiaWindow_ViewModel);
historyWindow.Show();
historyWindow.Activate();
System.Windows.Threading.Dispatcher.Run();
}
#endregion // History Thread
//...
}
and 'Wpis' Class look like:
public class Wpis
{
private DateTime date;
public DateTime Date // normal get/ set
private ObservableCollection<Produkt> listaZakupow;
public ObservableCollection<Produkt> ListaZakupow // normal get/set
public Wpis(ObservableCollection<Produkt> listaZakupow)
{
date = DateTime.Now;
this.listaZakupow = listaZakupow;
}
public void Finalizuj(Historia historia)
{
//NOT! - Thread Safe
// EXCEPTION!
// NotSupportedException
// This type of CollectionView does not support changes SourceCollection collection from a thread other than the Dispatcher.
historia.Add(this);
}
private void DoDispatchedAction(Action action)
{
if (currentDispatcher.CheckAccess())
{
action.Invoke();
}
else
{
currentDispatcher.Invoke(DispatcherPriority.DataBind, action);
}
}
}
And when I don't run thread (historyThread) I can normal do method 'Finalizuj' many times
but when I run Thread I can't add anything to list (can't run method - 'Finalizuj')
And VS show me exception about:
NonSupportedException was unhandled
This type of CollectionView does not support changes SourceCollection collection from a thread other than the Dispatcher.
I dont really know what i do wrong.
What i need to add to my project?
In short:
In main Thread - I have object_1 (typeof: ObservableCollection)
In second Thread - I want add anothrer object (typeof: Wpis : ObservableCollection) to object_1
but I get the abovementioned exception
The Exception is telling you exactly what you need to do. You cannot modify the UI from a different thread. I'm assuming that your Observable collection is bound somewhere in your XAML.
You will need to obtain a reference to your MainWindow class so you can access the Dispatcher thread.
public static MainWindow Current { get; private set; }
public MainWindow()
{
Current = this;
//...
}
Then use the Dispatcher thread to modify your collection:
MainWindow.Current.Dispatcher.Invoke((Action)(() =>
{
historia.Add(this);
}));

C# callback on new thread

i am having trouble creating a callback on a newly started thread.
I have 2 classes, an API, and the Form.cs. I start a thread running a method in API, from Form.cs, i want to notify a method in Form.cs from inside the method in API.
I am familiar with delegation in Obj-C, but not in C#.
I only included the relevant code.
public partial class Main: Form
{
private Api Connect = new Api();
private void StartStopButton_Click(object sender, EventArgs e)
{
//new thread
Thread ThreadConnect = new Thread(Connect.startAttemptingWithUsername);
ThreadConnect.Start();
}
public void AttemptingWithPasswordMessage(string password)
{
// i want to notify this method from the API
}
}
class Api : UserAgent
{
public void startAttemptingWithUsername()
{
_shouldStop = false;
while (!_shouldStop)
{
Console.WriteLine(username);
// How would i notify AttemptingWithPasswordMessage from here?
System.Threading.Thread.Sleep(1000);
}
}
}
Provide an event to your other class, and fire that event whenever it is relevant based on the processing:
class Api : UserAgent
{
public event Action<string> SomeEvent;//TODO give better name
public void startAttemptingWithUsername()
{
_shouldStop = false;
while (!_shouldStop)
{
Console.WriteLine(username);
var handler = SomeEvent;
if (handler != null)
handler("asdf");
// How would i notify AttemptingWithPasswordMessage from here?
System.Threading.Thread.Sleep(1000);
}
}
}
Then add a handler for that event: (And marshal back to the UI thread)
private void StartStopButton_Click(object sender, EventArgs e)
{
//new thread
Thread ThreadConnect = new Thread(Connect.startAttemptingWithUsername);
ThreadConnect.Start();
Connect.SomeEvent += (data) => Invoke(
new Action(()=>AttemptingWithPasswordMessage(data)));
}

Return feedback from an event which is being waited on

In it's simplicity what I am trying to do is handle "Doing Something" by firing off a process on a seperate thread to do what I need to do and waiting for an event to be raised to say "I have finished doing what I need to do". In the EventArgs though I will have a property for any errors which may be encountered during the process. Here is a simplified example of my situation.
public class MessageHandler
{
private AutoResetEvent MessageHasSent = new AutoResetEvent(false);
public void SendMessage()
{
MessageSender ms = new MessageSender();
ms.MessageSent += new EventHandler<MessageSentEventArgs>(MessageHandler_MessageSent);
Thread t = new Thread(ms.Send());
t.Start();
MessageHasSent.WaitOne();
//Do some check here
//Same again but for "Message recieved"
}
void MessageHandler_MessageSent(object sender, MessageSentEventArgs e)
{
if (e.Errors.Count != 0)
{
//What can I do here to return to the next step after waitone?
}
else
MessageHasSent.Set();
}
}
public class MessageSender
{
public event EventHandler<MessageSentEventArgs> MessageSent;
public void Send()
{
//Do some method which could potentiallialy return a List<Error>
MessageSent(this, new MessageSentEventArgs() { Errors = new List<Error>() });
}
}
public class Error { }
public class MessageSentEventArgs : EventArgs
{
public List<Error> Errors;
}
Essentially once the event has been raised from Send the code will continute, however I want some way of the event giving feedback, potentially using the MessageHasSent. I have tried different methods, I thought if I called Close instead of Set it would perhaps allow me to access something such as IsClosed. You could throw an exception or set a flag outside of the scope of the event to check but I feel like this is dirty.
Any suggestions?
Using the TPL isn't applicable in my case as I am using .NET 3.5.
Since it seems that this entire section of code is already running in a background thread, and you're doing nothing more than starting up a new thread just so that you can wait for it to finish, you'd be better off just calling Send directly, rather than asynchronously.
You don't need to fire off an event when you're completed.
You don't need to signal the main thread when it needs to continue.
You don't need to log the exceptions in a List, you can just throw them and catch them in SendMessage with a try/catch block.
This will do what you want:
public class MessageHandler
{
private AutoResetEvent MessageHasSent = new AutoResetEvent(false);
private bool IsSuccess = false;
public void SendMessage()
{
MessageSender ms = new MessageSender();
ms.MessageSent += new EventHandler<MessageSentEventArgs>(MessageHandler_MessageSent);
Thread t = new Thread(ms.Send());
t.Start();
MessageHasSent.WaitOne();
if(IsSuccess)
//wohooo
else
//oh crap
//Same again but for "Message recieved"
}
void MessageHandler_MessageSent(object sender, MessageSentEventArgs e)
{
IsSuccess = e.Errors.Count == 0;
MessageHasSent.Set();
}
}
public class MessageSender
{
public event EventHandler<MessageSentEventArgs> MessageSent;
public void Send()
{
//Do some method which could potentiallialy return a List<Error>
MessageSent(this, new MessageSentEventArgs() { Errors = new List<Error>() });
}
}
public class Error { }
public class MessageSentEventArgs : EventArgs
{
public List<Error> Errors;
}

Categories