I am trying to have a system.timer calculate a object and return it to the calling tread in C#. If I calculate it the main trad then i get the object, but if I try to calculate the same in a system.timer I do not get any value back.
How to return a object from a system.timer an use it in the main tread?
public MainWindow()
{
Timer execute = new Timer();
execute.Interval = 5000;
execute.Elapsed = UpdatePending;
execute.Start();
InitializeComponent();
}
private void UpdatePending(Object source, System.Timers.ElapsedEventArgs e)
{
DataTable pending = new Repository().GetPending();
if (pending?.Rows.Count > 0)
{
dataGrid_Pending.DataContext = pending;
}
}
The dataGrid_Pending is not updating when I do this, but it is if I run the code in the main tread:
DataTable pending = new Repository().GetPending();
if (pending?.Rows.Count > 0)
{
dataGrid_Pending.DataContext = pending;
}
CodePudding user response:
I made this Helper Class
to run things to the UI Thread
for my WPF
Application
public static class InvokeUI
{
/// <summary>
/// Execute an Action in the Appropriate UI Thread
/// <para>Will Invoke the UI if you are not already in the Appropriate UI Thread</para>
/// <para>Runs Synchronously</para>
/// </summary>
/// <param name="action"></param>
public static void CheckAccess(Action action)
{
try
{
Dispatcher dispatcher = Application.Current.Dispatcher;
if (dispatcher.CheckAccess())
{
action();
return;
}
dispatcher.Invoke(delegate
{
try
{
action();
}
catch (Exception ex)
{
// Log This Error
}
}, DispatcherPriority.Render);
}
catch
{
// The Dispatcher might throw here during closing and after the UI has already been disposed
}
}
}
You use it like this
InvokeUI.CheckAccess(() =>
{
dataGrid_Pending.DataContext = pending;
// Your UI Update
});
This is option number 4
from the answer JonasH
provided.
CodePudding user response:
How to return a object from a system.timer an use it in the main tread?
You need to politely ask the UI thread to run some code on your behalf. There are many ways to do this
- SynchronizationContext.Post
- Control.Invoke
- TaskScheduler.FromCurrentSynchronizationContext
- Dispatcher.Invoke
Some of these require you to save an object to use in your timer event. If you are using WPF the dispatcher is probably easiest to use, since you can use the static Application.Current.Dispatcher
to get it:
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)(() => DataContext = pending));
Note that whenever you are dealing with multiple threads, and timers.timer will by default invoke the event handler on a thread pool thread, you cannot access the UI, and you have to ensure the code is thread safe.
You might consider using a DispatcherTimer
instead, since this will invoke events on the UI thread, removing the problem. This is the best approach if your event handler is fairly fast, say ~50ms or so.
If it is slower it might be more useful to run the event on a background thread, but then you need to worry about what will happen if one event handler does not finish before the next tick occur, in addition to any thread safety concerns.