Memory Leak caused by Task in ViewModel - c#

I have the following code, it causes a memory leak.
The problem is the task, when I remove it, everything is fine and the View as well as the ViewModel are GCed. It seems like the Task is keeping a reference to UpdateTimeDate and hence the ViewModel. I tried various things, but none have worked, hoping someone has any idea or explanation why it is the case.
public class HeaderViewModel : Observable, IDisposableAsync
{
public HeaderViewModel (IDateTimeProvider dateTimeProvider)
{
TokenSource = new CancellationTokenSource();
ATask = Task.Run(
async () =>
{
while(!TokenSource.Token.IsCancellationRequested)
{
UpdateTimeData();
await Task.Delay(800);
}
IsDisposed = true;
},
TokenSource.Token);
UpdateTimeData();
void UpdateTimeData()
{
TimeText = dateTimeProvider.Now.ToString("HH:mm:ss");
DateText = dateTimeProvider.Now.ToString("dd.MM.yyyy");
}
}
public CancellationTokenSource TokenSource { get; set; }
public bool IsDisposed { get; set; }
public string TimeText
{
get => Get<string>();
set => Set(value);
}
public string DateText
{
get => Get<string>();
set => Set(value);
}
private Task ATask { get; set; }
public async Task Dispose()
{
TokenSource.Cancel();
while(!IsDisposed)
{
await Task.Delay(50);
}
TokenSource.Dispose();
ATask.Dispose();
ATask = null;
TokenSource = null;
}
}
This is the Timer based solution, it also causes a memory leak:
public class HeaderViewModel : Observable, IDisposableAsync
{
public HeaderViewModel(IDateTimeProvider dateTimeProvider)
{
DateTimeProvider = dateTimeProvider;
ATimer = new Timer(800)
{
Enabled = true
};
UpdateTimeData(this, null);
ATimer.Elapsed += UpdateTimeData;
}
public string TimeText
{
get => Get<string>();
set => Set(value);
}
public string DateText
{
get => Get<string>();
set => Set(value);
}
public bool IsDisposed { get; set; }
private IDateTimeProvider DateTimeProvider { get; }
private Timer ATimer { get; }
public async Task Dispose()
{
ATimer.Stop();
await Task.Delay(1000);
ATimer.Elapsed -= UpdateTimeData;
ATimer.Dispose();
IsDisposed = true;
}
private void UpdateTimeData(object sender, ElapsedEventArgs elapsedEventArgs)
{
TimeText = DateTimeProvider.Now.ToString("HH:mm:ss");
DateText = DateTimeProvider.Now.ToString("dd.MM.yyyy");
}
}

I found a solution. Thanks to keuleJ, he posted the comment that lead me to it. So the Task or Timer is capturing an instance of the ViewModel when you create either of them. The way to prevent it is to make a WeakReference and use that:
public class HeaderViewModel : Observable, IDisposableAsync
{
public HeaderViewModel(IDateTimeProvider dateTimeProvider)
{
DateTimeProvider = dateTimeProvider;
UpdateTimeData();
var weakReference = new WeakReference(this);
Task.Run(
async () =>
{
while(!((HeaderViewModel)weakReference.Target).IsDisposing)
{
((HeaderViewModel)weakReference.Target).UpdateTimeData();
await Task.Delay(800);
}
((HeaderViewModel)weakReference.Target).IsDisposed = true;
});
}
public bool IsDisposed { get; set; }
public string TimeText
{
get => Get<string>();
set => Set(value);
}
public string DateText
{
get => Get<string>();
set => Set(value);
}
private IDateTimeProvider DateTimeProvider { get; }
private bool IsDisposing { get; set; }
public async Task Dispose()
{
IsDisposing = true;
while(!IsDisposed)
{
await Task.Delay(50);
}
}
private void UpdateTimeData()
{
TimeText = DateTimeProvider.Now.ToString("HH:mm:ss");
DateText = DateTimeProvider.Now.ToString("dd.MM.yyyy");
}
}
Note that I also could not make a local variable out of
(HeaderViewModel)weakReference.Target
As soon as I did that some magic seems to happen and the instance would be captured again.

It appears that your Dispose task never returns which is why your object is remaining in memory. I tracked down the issue to the
await Task.Delay(1000)
if you change it per this post https://stackoverflow.com/a/24539937/3084003 it will work
await Task.Delay(1000).ConfigureAwait(false);

Related

How to design a processor that loads objects from a runtime configuration

I am implementing a scheduler. As part of its core logic it processes a custom object Schedule. Basically it iterates over an array of schedules and try to process it. The problem is who ever creates a Schedule needs to register it with the container using an ISchedule interface. My Scheduler then pulls all the ISchedule references from the container. SO far this is working but it does not have the flexibility of loading the schedules runtime. What design and implementation I can adapt to implement a Scheduler that can load those Schedules run time. I am giving some sample code.
Something that is coming to my mind is having the developers writing a json representation of the Schedules and put that inside a config or implementing an endpoint that returns that config to the Scheduler. But can I avoid this? I want the Scheduler to be completely agonistic of developer code.
You can use the factory to register information about schedule classes. And dynamically change the call interval by finding the schedule by Id.
You register in the container: <IScheduleFactory,ScheduleFactory> and <IScheduleManager,ScheduleManager>
public interface ISchedule
{
public string Id { get; set; }
public TimeSpan Interval { get; set; }
public DateTime? LastExecution { get; set; }
public bool CanStart { get; }
void Start();
void Stop();
}
public sealed class Schedule : ISchedule
{
public string Id { get; set; }
public TimeSpan Interval { get; set; }
public DateTime? LastExecution { get; set; }
public bool CanStart {
get
{
lock (_sync)
{
return !LastExecution.HasValue || LastExecution.Value.Add(Interval) >= DateTime.UtcNow;
}
}
}
private readonly object _sync = new object();
public void Start()
{
lock (_sync)
{
if (!LastExecution.HasValue || LastExecution.Value.Add(Interval) >= DateTime.UtcNow)
{
// DO WORK
LastExecution = DateTime.UtcNow;
}
}
}
public void Stop()
{
throw new NotImplementedException();
}
}
public interface IScheduleFactory
{
ISchedule Create();
}
public sealed class ScheduleFactory: IScheduleFactory
{
private readonly IScheduleManager _manager;
public ScheduleFactory(IScheduleManager manager)
{
_manager = manager;
}
public ISchedule Create()
{
var schedule = new Schedule();
_manager.Register(schedule);
return schedule;
}
}
public interface IScheduleManager
{
void Register(ISchedule schedule);
ISchedule Get(string id);
void Start();
void Stop();
}
public sealed class ScheduleManager : IScheduleManager
{
private readonly Dictionary<string,ISchedule> _items = new Dictionary<string, ISchedule>();
private readonly object _sync = new object();
public void Register(ISchedule schedule)
{
lock (_sync)
{
if (_items.ContainsKey(schedule.Id))
_items.Add(schedule.Id, schedule);
}
}
public ISchedule Get(string id)
{
lock (_sync)
{
if (_items.ContainsKey(id))
return _items[id];
}
return null;
}
private bool _isStart;
public void Start()
{
_isStart = true;
while (_isStart)
{
ISchedule[] array = null;
lock (_sync)
{
array = _items.Values.ToArray();
}
foreach (var schedule in array)
{
if (schedule.CanStart)
Task.Factory.StartNew(()=>schedule.Start());
}
}
}
public void Stop()
{
_isStart = false;
}
}

Just a enquiry, about Invoke() in Main thread

I have a property that can be modified from a method with Invoke()(from thread) and other without invoke() in the same class.
what happen if they are called in the same moment?
This is possible? Since can affect the condition in some method.
For example:
public class Test{
public bool testBool { get; set; }
public void MethodWIthInvoke(){
this.Invoke(new Action(() =>
{
if (testBool)
{
testBool = false;
}
}));
}
public void Method(){
if (testBool)
{
testBool = false;
}
}
}
I am not sure why do you need to make the code this way, anyway, since both of the methods will be called from the same thread then it will be fine. I want to suggest another way to write your code as follows:
public class Test{
public bool testBool { get; set; }
public void Method()
{
if (this.InvokeRequired)
{
this.Invoke(new Action(() =>
{
if (testBool)
{
testBool = false;
}
}));
}
else
{
if (testBool)
{
testBool = false;
}
}
}
}

Async Function Only Updating One "Await"

Excuse my ignorance on this subject, but I'm not schooled in asynchronous programming. However, I believe my code is close to achieving what I need it to do.
Basically the code below only works for the very first
C61 = new BridgeViewModel("C61");
await C61.Initialize();
statements. None of the other items are returning any data to the bound .xaml and I have no idea why. Does each one need to be in its own Initialize function?
Any help is greatly appreciated.
Code:
namespace SentinelApp
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var vm = new MainViewModel();
DataContext = vm;
vm.Initialize();
}
}
public class MainViewModel
{
public readonly string[] Bridges = { "C61", "C62", "C63", "C68", "C69", "K71", "K72", "K73", "K74", "T91", "GN01", "GE01", "GA01" };
public async Task Initialize()
{
C61 = new BridgeViewModel("C61");
await C61.Initialize();
C62 = new BridgeViewModel("C62");
await C62.Initialize();
C63 = new BridgeViewModel("C63");
await C63.Initialize();
C68 = new BridgeViewModel("C68");
await C68.Initialize();
C69 = new BridgeViewModel("C69");
await C69.Initialize();
K71 = new BridgeViewModel("K71");
await K71.Initialize();
K72 = new BridgeViewModel("K72");
await K72.Initialize();
K73 = new BridgeViewModel("K73");
await K73.Initialize();
K74 = new BridgeViewModel("K74");
await K74.Initialize();
T91 = new BridgeViewModel("T91");
await T91.Initialize();
GA01 = new BridgeViewModel("GA01");
await GA01.Initialize();
GE01 = new BridgeViewModel("GE01");
await GE01.Initialize();
GN01 = new BridgeViewModel("GN01");
await GN01.Initialize();
}
public BridgeViewModel C61 { get; set; }
public BridgeViewModel C62 { get; set; }
public BridgeViewModel C63 { get; set; }
public BridgeViewModel C68 { get; set; }
public BridgeViewModel C69 { get; set; }
public BridgeViewModel K71 { get; set; }
public BridgeViewModel K72 { get; set; }
public BridgeViewModel K73 { get; set; }
public BridgeViewModel K74 { get; set; }
public BridgeViewModel T91 { get; set; }
public BridgeViewModel GA01 { get; set; }
public BridgeViewModel GE01 { get; set; }
public BridgeViewModel GN01 { get; set; }
}
public class BridgeViewModel : ViewModel
{
private readonly ClientWrapper _client;
private readonly string _bridge;
private readonly Timer _timer = new Timer();
public BridgeViewModel(string bridge)
{
_client = new ClientWrapper();
_bridge = bridge;
}
public async Task Initialize()
{
await _client.Connect(_bridge);
await _client.SendMessage(new SessionStart("3", "25").CreateBridgeMessage());
_timer.Interval = 1000;
_timer.Elapsed += Update;
_timer.Start();
}
private async void Update(object sender, ElapsedEventArgs e)
{
try {
var response = await _client.SendMessage("BS~RESERVE~STATS~REQ~" + _bridge + "~0");
var split = response.Split('~');
Timestamp = split[4].Substring(0, 2) + ":" + split[4].Substring(2, 2) + ":" + split[4].Substring(4, 2);
FreePorts = split[6];
LongestHold = TimeSpan.FromSeconds(int.Parse(split[15])).ToString("hh:mm");
Bells = split[12];
Signals = split[8];
} catch { }
}
private string _timestamp;
public string Timestamp
{
get { return _timestamp; }
set { _timestamp = value; RaisePropertyChanged(); }
}
private string _bells;
public string Bells
{
get { return _bells; }
set { _bells = value; RaisePropertyChanged(); }
}
private string _signals;
public string Signals
{
get { return _signals; }
set { _signals = value; RaisePropertyChanged(); }
}
private string _freeports;
public string FreePorts
{
get { return _freeports; }
set { _freeports = value; RaisePropertyChanged(); }
}
private string _longesthold;
public string LongestHold
{
get { return _longesthold; }
set { _longesthold = value; RaisePropertyChanged(); }
}
}
}
Instead of using your async method inside a constructor, register and use it inside the FrameworkElement.Loaded event where you can await:
public MainWindow()
{
InitializeComponent();
Loaded += InitializeOnLoaded;
}
public async void InitializeOnLoaded(object sender, RoutedEventArgs e)
{
var vm = new MainViewModel();
DataContext = vm;
await vm.InitializeAsync();
}

Job Scheduler Implementation

Well i have created a job scheduler that has many capabilities; However i really want to use .Net 4.5 Async/Await feature with it in order to wait for a job to finish executing.
-Code
Scheduler.cs
public abstract class Scheduler
{
#region Fields && Properties
private readonly List<Job> _jobs = new List<Job>();
private readonly Random _rand = new Random();
private Job _currentlyExecutingJob;
private Thread _workingThread;
public bool? Parallel { get; private set; }
public DateTimeOffset NextExecutionTime { get; private set; }
public string ID { get; set; }
public abstract Task JobTrigger(Job job);
public abstract void UnobservedException(Exception exception, Job job);
#endregion
#region Ctor
protected Scheduler(bool parallel) { Parallel = parallel; }
#endregion
#region Fluent
public Scheduler Start()
{
if (Equals(_workingThread, null)) {
_workingThread = new Thread(ReviewJobs);
_workingThread.Start();
}
return this;
}
public Scheduler Stop()
{
if (!Equals(_workingThread, null)) {
_workingThread.Abort();
_workingThread = null;
}
return this;
}
#endregion
#region Events
private void ReviewJobs()
{
while (!Equals(_workingThread, null)) {
IEnumerable<Job> jobsToExecute = from job in _jobs
where job.NextExecutionTime <= DateTimeOffset.Now
orderby job.Priority
select job;
if (!jobsToExecute.Any()) {
Thread.Sleep(100);
continue;
}
try {
foreach (Job job in jobsToExecute) {
Job o = _currentlyExecutingJob = job;
if (Parallel != null && (bool)Parallel) {
JobTrigger(o);
} else {
JobTrigger(o).Wait();
}
if (!o.Repeat)
_jobs.Remove(o);
else if (o.Interval != null)
o.NextExecutionTime = DateTimeOffset.Now.Add((TimeSpan)(o.RandomizeExecution
? TimeSpan.FromSeconds(_rand.Next((int)((TimeSpan)o.Interval).TotalSeconds, ((int)((TimeSpan)o.Interval).TotalSeconds + 30)))
: o.Interval));
}
} catch (Exception exception) {
UnobservedException(exception, _currentlyExecutingJob);
} finally {
NextExecutionTime = (from job in _jobs
where job.NextExecutionTime <= DateTimeOffset.Now
orderby job.Priority
select job.NextExecutionTime).FirstOrDefault();
}
}
}
#endregion
#region Helper Methods
public Job GetJob(string id) { return _jobs.Find(job => job.ID == id); }
public Job GetJob(Job job) { return _jobs.Find(x => x == job); }
public IEnumerable<Job> GetAllJobs() { return _jobs; }
public void AddJob(Job job, bool overwrite = false)
{
Job existingJob = GetJob(job);
if (null != existingJob) {
if (overwrite) {
_jobs.RemoveAll(jobs => jobs == existingJob);
_jobs.Add(job);
} else {
_jobs.Add(job);
}
} else {
_jobs.Add(job);
}
}
public bool RemoveJob(string id)
{
Job existingJob = GetJob(id);
if (null != existingJob) {
return _jobs.Remove(existingJob);
}
return false;
}
public bool RemoveJob(Job job)
{
Job existingJob = GetJob(job);
if (null != existingJob) {
return _jobs.Remove(existingJob);
}
return false;
}
public void RemoveAllJobs() { _jobs.RemoveRange(0, _jobs.Count); }
#endregion
}
Job.cs
public class Job
{
public string ID { get; set; }
public TimeSpan Interval { get; private set; }
public DateTimeOffset NextExecutionTime { get; set; }
public int Priority { get; set; }
public bool Repeat { get; private set; }
public bool RandomizeExecution { get; set; }
public object Data { get; set; }
#region Fluent
public Job RunOnceAt(DateTimeOffset executionTime)
{
NextExecutionTime = executionTime;
Repeat = false;
return this;
}
public Job RepeatFrom(DateTimeOffset executionTime, TimeSpan interval)
{
NextExecutionTime = executionTime;
Interval = interval;
Repeat = true;
return this;
}
#endregion
}
-Usage
public class SchedulerUsage : Scheduler
{
public SchedulerUsage(bool parallel) : base(parallel) {
}
public override async Task JobTrigger(Job job)
{
switch (job.ID) {
//Handle Jobs
}
}
public override void UnobservedException(Exception exception, Job job)
{
//Handle Exceptions
}
/// <summary>
/// Example of adding job
/// </summary>
public void ExampleUsage()
{
Job job = new Job
{
ID = "ID",
Data = "ATTACH SOME DATA"
}.RunOnceAt(DateTimeOffset.Now.AddSeconds(7));
//Add the job... [HERE IS WHAT I WANT TO ACHIVE]
/*await*/base.AddJob(job);
//Start the scheduler...
base.Start();
}
}
Question: How to use async/await to await the execution of a job in my implementation above.
NB: I am sorry if my question seems to be a bit complicated but it is very necessary for me so please be patience with me.

Invoke a delegate on the main thread in a tiered architecture

I have a background process that i want to regularly maintain the state of gps location. I am not clear on how to invoke a delegate on the main thread in the ui layer when the threaded method is in another class. Here is sample code. My form launches the thread on load:
public partial class MainScreen : Form
{
.
. // form stuff
.
private void MainScreen_Load(object sender, EventArgs e)
{
var gpsStatusManager = new GpsStatusManager();
Thread t = new Thread(gpsStatusManager.UpdateLocation);
t.IsBackground = true;
t.Start();
}
delegate void GpsDataParameterDelegate(GpsStatus value);
public void UpdateGpsStatus(GpsStatus value)
{
if (InvokeRequired)
{
// We're not in the UI thread, so we need to call BeginInvoke
BeginInvoke(new GpsDataParameterDelegate(UpdateGpsStatus), new object[] { value });
return;
}
// Must be on the UI thread if we've got this far
gpsStatus.SetGpsStatus(value);
}
}
I have a domain object class for the gps information:
public class GpsStatus
{
public void SetGpsStatus(GpsStatus gpsStatus)
{
Latitude = gpsStatus.Latitude;
Longitude = gpsStatus.Longitude;
CurrentDateTime = gpsStatus.CurrentDateTime;
NumberOfSatellites = gpsStatus.NumberOfSatellites;
TotalNumberSatellites = gpsStatus.TotalNumberSatellites;
}
public float Latitude { get; private set; }
public float Longitude { get; private set; }
public DateTime CurrentDateTime { get; private set; }
public int NumberOfSatellites { get; private set; }
public int TotalNumberSatellites { get; private set; }
}
Then, my manager class where i update status in the secondary thread:
public class GpsStatusManager
{
private GpsStatus _gpsStatus;
public void UpdateLocationx()
{
while (UpdateGpsData())
{
Thread.Sleep(2000);
}
}
private bool UpdateGpsData()
{
SError error;
SGpsPosition gpsPosition;
try
{
if (CApplicationAPI.GetActualGpsPosition(out error, out gpsPosition, true, 0) != 1)
return false;
}
catch (Exception)
{
return false;
}
var numberOfSatellites = gpsPosition.Satellites;
var totalSatellites = gpsPosition.satellitesInfo;
var datetime = gpsPosition.Time;
var lat = gpsPosition.Latitude;
var lon = gpsPosition.Longitude;
_gpsStatus.SetGpsStatus(lat, lon, datetime, numberOfSatellites, totalSatellites);
//How do I invoke the delegate to send the _gpsStatus data to my main thread?
return true;
}
}
Thanks for any assistance.
Here's one way to do it, just off the top of my head:
public class GpsStatusEventArgs : EventArgs
{
public GpsStatus Status { get; private set; }
public GpsStatusEventArgs(GpsStatus status)
{
Status = status;
}
}
public class GpsStatusManager
{
...
public event EventHandler<GpsStatusEventArgs> GpsStatusUpdated;
private void OnGpsStatusUpdated(GpsStatus gpsStatus)
{
EventHandler<GpsStatusEventArgs> temp = GpsStatusUpdated;
if (temp != null)
temp.Invoke(this, new GpsStatusEventArgs(gpsStatus));
}
}
public partial class MainScreen : Form
{
...
private void MainScreen_Load(object sender, EventArgs e)
{
var gpsStatusManager = new GpsStatusManager();
gpsStatusManager.GpsStatusUpdated += new EventHandler<GpsStatusEventArgs>(GpsStatusManager_GpsStatusUpdated);
...
}
private void GpsStatusManager_GpsStatusUpdated(object sender, GpsStatusEventArgs e)
{
UpdateGpsStatus(e.Status);
}
...
}
Then add this to the bottom of UpdateGpsData:
OnGpsStatusUpdated(_gpsStatus);
You should use the SynchronizationContext class.
In the UI thread (in any class), set a field (perhaps static) to SynchronizationContext.Current.
You can then call Send or Post on the saved instance to execute code on the UI thread.
Here is another approach using the ISynchronizeInvoke interface. This is the same pattern the System.Timers.Timer class uses to raise the Elapsed event.
public class GpsStatusManager
{
public ISynchronizeInvoke SynchronizingObject { get; set; }
public event EventHandler Update;
public void UpdateGpsData()
{
// Code omitted for brevity.
OnUpdate(_gpsStatus);
return true;
}
private OnUpdate(GpsStatus status)
{
if (SynchronizingObject != null && SynchronizingObject.IsInvokeRequired)
{
ThreadStart ts = () => { OnUpdate(status); };
SynchronizingObject.Invoke(ts, null);
}
else
{
if (Update != null)
{
Update(this, status);
}
}
}
public class UpdateEventArgs : EventArgs
{
public GpsStatus Status { get; set; }
}
}

Categories