Free cookie consent management tool by TermsFeed Policy Generator

source: branches/HivePerformance/sources/HeuristicLab.Clients.Hive/3.3/RefreshableJob.cs @ 9674

Last change on this file since 9674 was 9539, checked in by ascheibe, 12 years ago

#2030 merged changes of trunk into branch

File size: 23.4 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2013 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
4 *
5 * This file is part of HeuristicLab.
6 *
7 * HeuristicLab is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * HeuristicLab is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
19 */
20#endregion
21
22using System;
23using System.Collections.Generic;
24using System.ComponentModel;
25using System.Drawing;
26using System.Linq;
27using HeuristicLab.Collections;
28using HeuristicLab.Common;
29using HeuristicLab.Core;
30using HeuristicLab.MainForm;
31
32namespace HeuristicLab.Clients.Hive {
33  public class RefreshableJob : IHiveItem, IDeepCloneable, IContent, IComparable<RefreshableJob>, IDisposable {
34    private JobResultPoller jobResultPoller;
35    private ConcurrentTaskDownloader<ItemTask> jobDownloader;
36    private object locker = new object();
37    private object downloadFinishedLocker = new object();
38    object jobResultReceivedLocker = new object();
39
40    public bool IsProgressing { get; set; }
41
42    private Job job;
43    public Job Job {
44      get { return job; }
45      set {
46        if (value != job) {
47          if (value == null)
48            throw new ArgumentNullException();
49
50          if (job != null) DeregisterJobEvents();
51          job = value;
52          if (job != null) {
53            RegisterJobEvents();
54            job_PropertyChanged(job, new PropertyChangedEventArgs("Id"));
55          }
56          OnJobChanged();
57          OnToStringChanged(this, EventArgs.Empty);
58          job_ItemImageChanged(this, EventArgs.Empty);
59        }
60      }
61    }
62
63    private ItemCollection<HiveTask> hiveTasks;
64    public ItemCollection<HiveTask> HiveTasks {
65      get { return hiveTasks; }
66      set {
67        if (hiveTasks != value) {
68          if (hiveTasks != null) DeregisterHiveTasksEvents();
69          hiveTasks = value;
70          if (hiveTasks != null) RegisterHiveTasksEvents();
71          OnHiveTasksChanged();
72        }
73      }
74    }
75
76    private ExecutionState executionState;
77    public ExecutionState ExecutionState {
78      get { return executionState; }
79      internal set {
80        if (executionState != value) {
81          executionState = value;
82          OnExecutionStateChanged();
83        }
84      }
85    }
86
87    private TimeSpan executionTime;
88    public TimeSpan ExecutionTime {
89      get { return executionTime; }
90      internal set {
91        if (executionTime != value) {
92          executionTime = value;
93          OnExecutionTimeChanged();
94        }
95      }
96    }
97
98    private bool refreshAutomatically;
99    public bool RefreshAutomatically {
100      get { return refreshAutomatically; }
101      set {
102        lock (locker) {
103          if (refreshAutomatically != value) {
104            refreshAutomatically = value;
105            OnRefreshAutomaticallyChanged();
106          }
107          if (RefreshAutomatically) {
108            if (this.HiveTasks != null && this.HiveTasks.Count > 0 && (jobResultPoller == null || !jobResultPoller.IsPolling)) {
109              StartResultPolling();
110            }
111          } else {
112            PauseResultPolling();
113          }
114        }
115      }
116    }
117
118    // indicates if download button is enabled
119    private bool isDownloadable = true;
120    public bool IsDownloadable {
121      get { return isDownloadable; }
122      set {
123        if (value != isDownloadable) {
124          isDownloadable = value;
125          OnIsDownloadableChanged();
126        }
127      }
128    }
129
130    // if true, all control buttons should be enabled. otherwise disabled
131    private bool isControllable = true;
132    public bool IsControllable {
133      get { return isControllable; }
134      private set {
135        if (value != isControllable) {
136          isControllable = value;
137          OnIsControllableChanged();
138          if (this.hiveTasks != null) {
139            foreach (var hiveJob in this.hiveTasks) {
140              hiveJob.IsControllable = value;
141            }
142          }
143        }
144      }
145    }
146
147    // indicates if a user is allowed to share this experiment
148    private bool isSharable = true;
149    public bool IsSharable {
150      get { return isSharable; }
151      private set {
152        if (value != isSharable) {
153          isSharable = value;
154          OnIsSharableChanged();
155        }
156      }
157    }
158
159    private Progress progress;
160    public Progress Progress {
161      get { return progress; }
162      set {
163        this.progress = value;
164        OnIsProgressingChanged();
165      }
166    }
167
168
169    private ThreadSafeLog log;
170    public ILog Log {
171      get { return log; }
172    }
173
174    public StateLogListList StateLogList {
175      get { return new StateLogListList(this.GetAllHiveTasks().Select(x => x.StateLog)); }
176    }
177
178    #region Constructors and Cloning
179    public RefreshableJob() {
180      this.refreshAutomatically = false;
181      this.Job = new Job();
182      this.log = new ThreadSafeLog();
183      this.jobDownloader = new ConcurrentTaskDownloader<ItemTask>(Settings.Default.MaxParallelDownloads, Settings.Default.MaxParallelDownloads);
184      this.jobDownloader.ExceptionOccured += new EventHandler<EventArgs<Exception>>(jobDownloader_ExceptionOccured);
185      this.HiveTasks = new ItemCollection<HiveTask>();
186    }
187    public RefreshableJob(Job hiveJob) {
188      this.refreshAutomatically = true;
189      this.Job = hiveJob;
190      this.log = new ThreadSafeLog();
191      this.jobDownloader = new ConcurrentTaskDownloader<ItemTask>(Settings.Default.MaxParallelDownloads, Settings.Default.MaxParallelDownloads);
192      this.jobDownloader.ExceptionOccured += new EventHandler<EventArgs<Exception>>(jobDownloader_ExceptionOccured);
193      this.HiveTasks = new ItemCollection<HiveTask>();
194    }
195    protected RefreshableJob(RefreshableJob original, Cloner cloner) {
196      cloner.RegisterClonedObject(original, this);
197      this.Job = cloner.Clone(original.Job);
198      this.IsControllable = original.IsControllable;
199      this.log = cloner.Clone(original.log);
200      this.RefreshAutomatically = false; // do not start results polling automatically
201      this.jobDownloader = new ConcurrentTaskDownloader<ItemTask>(Settings.Default.MaxParallelDownloads, Settings.Default.MaxParallelDownloads);
202      this.jobDownloader.ExceptionOccured += new EventHandler<EventArgs<Exception>>(jobDownloader_ExceptionOccured);
203      this.HiveTasks = cloner.Clone(original.HiveTasks);
204      this.ExecutionTime = original.ExecutionTime;
205      this.ExecutionState = original.ExecutionState;
206    }
207    public IDeepCloneable Clone(Cloner cloner) {
208      return new RefreshableJob(this, cloner);
209    }
210    public object Clone() {
211      return this.Clone(new Cloner());
212    }
213    #endregion
214
215    #region JobResultPoller Events
216    public void StartResultPolling() {
217      if (jobResultPoller == null) {
218        jobResultPoller = new JobResultPoller(job.Id, Settings.Default.ResultPollingInterval);
219        RegisterResultPollingEvents();
220        jobResultPoller.AutoResumeOnException = false;
221      }
222
223      if (!jobResultPoller.IsPolling) {
224        jobResultPoller.Start();
225      }
226    }
227
228    public void StopResultPolling() {
229      if (jobResultPoller != null && jobResultPoller.IsPolling) {
230        refreshAutomatically = false;
231        jobResultPoller.Stop();
232        DeregisterResultPollingEvents();
233        jobResultPoller = null;
234      }
235    }
236
237    public void PauseResultPolling() {
238      if (jobResultPoller != null && jobResultPoller.IsPolling) {
239        jobResultPoller.Stop();
240      }
241    }
242
243    private void RegisterResultPollingEvents() {
244      jobResultPoller.ExceptionOccured += new EventHandler<EventArgs<Exception>>(jobResultPoller_ExceptionOccured);
245      jobResultPoller.JobResultsReceived += new EventHandler<EventArgs<IEnumerable<LightweightTask>>>(jobResultPoller_JobResultReceived);
246      jobResultPoller.IsPollingChanged += new EventHandler(jobResultPoller_IsPollingChanged);
247    }
248    private void DeregisterResultPollingEvents() {
249      jobResultPoller.ExceptionOccured -= new EventHandler<EventArgs<Exception>>(jobResultPoller_ExceptionOccured);
250      jobResultPoller.JobResultsReceived -= new EventHandler<EventArgs<IEnumerable<LightweightTask>>>(jobResultPoller_JobResultReceived);
251      jobResultPoller.IsPollingChanged -= new EventHandler(jobResultPoller_IsPollingChanged);
252    }
253    private void jobResultPoller_IsPollingChanged(object sender, EventArgs e) {
254      if (this.refreshAutomatically != jobResultPoller.IsPolling) {
255        this.refreshAutomatically = jobResultPoller.IsPolling;
256        OnRefreshAutomaticallyChanged();
257      }
258    }
259
260    private void jobResultPoller_JobResultReceived(object sender, EventArgs<IEnumerable<LightweightTask>> e) {
261      lock (jobResultReceivedLocker) {
262        foreach (LightweightTask lightweightTask in e.Value) {
263          HiveTask hiveTask = GetHiveTaskById(lightweightTask.Id);
264          if (hiveTask != null) {
265            // lastJobDataUpdate equals DateTime.MinValue right after it was uploaded. When the first results are polled, this value is updated
266            if (hiveTask.Task.State == TaskState.Offline && lightweightTask.State == TaskState.Waiting) {
267              hiveTask.Task.LastTaskDataUpdate = lightweightTask.LastTaskDataUpdate;
268            }
269
270            hiveTask.UpdateFromLightweightJob(lightweightTask);
271
272            if (!hiveTask.IsFinishedTaskDownloaded && !hiveTask.IsDownloading && hiveTask.Task.LastTaskDataUpdate < lightweightTask.LastTaskDataUpdate && (lightweightTask.State == TaskState.Finished || lightweightTask.State == TaskState.Aborted || lightweightTask.State == TaskState.Failed || lightweightTask.State == TaskState.Paused)) {
273              log.LogMessage(string.Format("Downloading task {0}", lightweightTask.Id));
274              hiveTask.IsDownloading = true;
275              jobDownloader.DownloadTaskData(hiveTask.Task, (localJob, itemJob) => {
276                lock (downloadFinishedLocker) {
277                  log.LogMessage(string.Format("Finished downloading task {0}", localJob.Id));
278                  HiveTask localHiveTask = GetHiveTaskById(localJob.Id);
279
280                  if (itemJob == null) {
281                    // something bad happened to this task. bad task, BAAAD task!
282                    localHiveTask.IsDownloading = false;
283                  } else {
284                    // if the task is paused, download but don't integrate into parent optimizer (to avoid Prepare)
285                    if (localJob.State == TaskState.Paused) {
286                      localHiveTask.ItemTask = itemJob;
287                    } else {
288                      if (localJob.ParentTaskId.HasValue) {
289                        HiveTask parentHiveTask = GetHiveTaskById(localJob.ParentTaskId.Value);
290                        parentHiveTask.IntegrateChild(itemJob, localJob.Id);
291                      } else {
292                        localHiveTask.ItemTask = itemJob;
293                      }
294                    }
295                    localHiveTask.IsDownloading = false;
296                    localHiveTask.Task.LastTaskDataUpdate = lightweightTask.LastTaskDataUpdate;
297                  }
298                }
299              });
300            }
301          }
302        }
303        GC.Collect(); // force GC, because .NET is too lazy here (deserialization takes a lot of memory)
304        if (AllJobsFinished()) {
305          this.ExecutionState = Core.ExecutionState.Stopped;
306          StopResultPolling();
307        }
308        UpdateTotalExecutionTime();
309        UpdateStatistics();
310        OnStateLogListChanged();
311        OnTaskReceived();
312      }
313    }
314
315    public HiveTask GetHiveTaskById(Guid jobId) {
316      foreach (HiveTask t in this.HiveTasks) {
317        var hj = t.GetHiveTaskByTaskId(jobId);
318        if (hj != null)
319          return hj;
320      }
321      return null;
322    }
323
324    private void UpdateStatistics() {
325      var jobs = this.GetAllHiveTasks();
326      job.JobCount = jobs.Count();
327      job.CalculatingCount = jobs.Count(j => j.Task.State == TaskState.Calculating);
328      job.FinishedCount = jobs.Count(j => j.Task.State == TaskState.Finished);
329      OnJobStatisticsChanged();
330    }
331
332    public bool AllJobsFinished() {
333      return this.GetAllHiveTasks().All(j => (j.Task.State == TaskState.Finished
334                                                   || j.Task.State == TaskState.Aborted
335                                                   || j.Task.State == TaskState.Failed)
336                                                   && !j.IsDownloading);
337    }
338
339    private void jobResultPoller_ExceptionOccured(object sender, EventArgs<Exception> e) {
340      OnExceptionOccured(sender, e.Value);
341    }
342    private void jobDownloader_ExceptionOccured(object sender, EventArgs<Exception> e) {
343      OnExceptionOccured(sender, e.Value);
344    }
345    public void UpdateTotalExecutionTime() {
346      this.ExecutionTime = TimeSpan.FromMilliseconds(this.GetAllHiveTasks().Sum(x => x.Task.ExecutionTime.TotalMilliseconds));
347    }
348    #endregion
349
350    #region Job Events
351    private void RegisterJobEvents() {
352      job.ToStringChanged += new EventHandler(OnToStringChanged);
353      job.PropertyChanged += new PropertyChangedEventHandler(job_PropertyChanged);
354      job.ItemImageChanged += new EventHandler(job_ItemImageChanged);
355      job.ModifiedChanged += new EventHandler(job_ModifiedChanged);
356    }
357
358    private void DeregisterJobEvents() {
359      job.ToStringChanged -= new EventHandler(OnToStringChanged);
360      job.PropertyChanged -= new PropertyChangedEventHandler(job_PropertyChanged);
361      job.ItemImageChanged -= new EventHandler(job_ItemImageChanged);
362      job.ModifiedChanged -= new EventHandler(job_ModifiedChanged);
363    }
364    #endregion
365
366    #region Event Handler
367    public event EventHandler RefreshAutomaticallyChanged;
368    private void OnRefreshAutomaticallyChanged() {
369      var handler = RefreshAutomaticallyChanged;
370      if (handler != null) handler(this, EventArgs.Empty);
371    }
372
373    public event EventHandler JobChanged;
374    private void OnJobChanged() {
375      var handler = JobChanged;
376      if (handler != null) handler(this, EventArgs.Empty);
377    }
378
379    public event EventHandler ModifiedChanged;
380    private void job_ModifiedChanged(object sender, EventArgs e) {
381      var handler = ModifiedChanged;
382      if (handler != null) handler(sender, e);
383    }
384
385    public event EventHandler ItemImageChanged;
386    private void job_ItemImageChanged(object sender, EventArgs e) {
387      var handler = ItemImageChanged;
388      if (handler != null) handler(this, e);
389    }
390
391    public event PropertyChangedEventHandler PropertyChanged;
392    private void job_PropertyChanged(object sender, PropertyChangedEventArgs e) {
393      this.IsSharable = job.Permission == Permission.Full;
394      this.IsControllable = job.Permission == Permission.Full;
395
396      var handler = PropertyChanged;
397      if (handler != null) handler(sender, e);
398    }
399
400    public event EventHandler ToStringChanged;
401    private void OnToStringChanged(object sender, EventArgs e) {
402      var handler = ToStringChanged;
403      if (handler != null) handler(this, e);
404    }
405
406    public event EventHandler IsDownloadableChanged;
407    private void OnIsDownloadableChanged() {
408      var handler = IsDownloadableChanged;
409      if (handler != null) handler(this, EventArgs.Empty);
410    }
411
412    public event EventHandler IsControllableChanged;
413    private void OnIsControllableChanged() {
414      var handler = IsControllableChanged;
415      if (handler != null) handler(this, EventArgs.Empty);
416    }
417
418    public event EventHandler IsSharableChanged;
419    private void OnIsSharableChanged() {
420      var handler = IsSharableChanged;
421      if (handler != null) handler(this, EventArgs.Empty);
422    }
423
424    public event EventHandler JobStatisticsChanged;
425    private void OnJobStatisticsChanged() {
426      var handler = JobStatisticsChanged;
427      if (handler != null) handler(this, EventArgs.Empty);
428    }
429
430    public event EventHandler<EventArgs<Exception>> ExceptionOccured;
431    private void OnExceptionOccured(object sender, Exception exception) {
432      log.LogException(exception);
433      var handler = ExceptionOccured;
434      if (handler != null) handler(sender, new EventArgs<Exception>(exception));
435    }
436
437    public event EventHandler StateLogListChanged;
438    private void OnStateLogListChanged() {
439      var handler = StateLogListChanged;
440      if (handler != null) handler(this, EventArgs.Empty);
441    }
442
443    public event EventHandler ExecutionTimeChanged;
444    protected virtual void OnExecutionTimeChanged() {
445      var handler = ExecutionTimeChanged;
446      if (handler != null) handler(this, EventArgs.Empty);
447    }
448
449    public event EventHandler ExecutionStateChanged;
450    protected virtual void OnExecutionStateChanged() {
451      var handler = ExecutionStateChanged;
452      if (handler != null) handler(this, EventArgs.Empty);
453    }
454    public event EventHandler TaskReceived;
455    protected virtual void OnTaskReceived() {
456      var handler = TaskReceived;
457      if (handler != null) handler(this, EventArgs.Empty);
458    }
459    public event EventHandler IsProgressingChanged;
460    private void OnIsProgressingChanged() {
461      var handler = IsProgressingChanged;
462      if (handler != null) handler(this, EventArgs.Empty);
463    }
464    #endregion
465
466    #region HiveTasks Events
467    private void RegisterHiveTasksEvents() {
468      this.hiveTasks.ItemsAdded += new CollectionItemsChangedEventHandler<HiveTask>(hivetasks_ItemsAdded);
469      this.hiveTasks.ItemsRemoved += new CollectionItemsChangedEventHandler<HiveTask>(hiveTasks_ItemsRemoved);
470      this.hiveTasks.CollectionReset += new CollectionItemsChangedEventHandler<HiveTask>(hiveTasks_CollectionReset);
471    }
472
473    private void DeregisterHiveTasksEvents() {
474      this.hiveTasks.ItemsAdded -= new CollectionItemsChangedEventHandler<HiveTask>(hivetasks_ItemsAdded);
475      this.hiveTasks.ItemsRemoved -= new CollectionItemsChangedEventHandler<HiveTask>(hiveTasks_ItemsRemoved);
476      this.hiveTasks.CollectionReset -= new CollectionItemsChangedEventHandler<HiveTask>(hiveTasks_CollectionReset);
477    }
478
479    private void hiveTasks_CollectionReset(object sender, CollectionItemsChangedEventArgs<HiveTask> e) {
480      foreach (var item in e.Items) {
481        item.StateLogChanged -= new EventHandler(item_StateLogChanged);
482      }
483      OnHiveTasksReset(e);
484    }
485
486    private void hiveTasks_ItemsRemoved(object sender, CollectionItemsChangedEventArgs<HiveTask> e) {
487      foreach (var item in e.Items) {
488        item.StateLogChanged -= new EventHandler(item_StateLogChanged);
489      }
490      OnHiveTasksRemoved(e);
491    }
492
493    private void hivetasks_ItemsAdded(object sender, CollectionItemsChangedEventArgs<HiveTask> e) {
494      foreach (var item in e.Items) {
495        item.StateLogChanged += new EventHandler(item_StateLogChanged);
496        item.IsControllable = this.IsControllable;
497      }
498      OnHiveTasksAdded(e);
499    }
500
501    private void item_StateLogChanged(object sender, EventArgs e) {
502      OnStateLogListChanged();
503    }
504    #endregion
505
506    public event EventHandler HiveTasksChanged;
507    protected virtual void OnHiveTasksChanged() {
508      if (this.HiveTasks != null && this.HiveTasks.Count > 0 && this.GetAllHiveTasks().All(x => x.Task.Id != Guid.Empty)) {
509        if (IsFinished()) {
510          this.ExecutionState = Core.ExecutionState.Stopped;
511          this.RefreshAutomatically = false;
512          StopResultPolling();
513        } else {
514          this.RefreshAutomatically = true;
515        }
516      }
517
518      var handler = HiveTasksChanged;
519      if (handler != null) handler(this, EventArgs.Empty);
520    }
521
522    public event EventHandler Loaded;
523    public virtual void OnLoaded() {
524      this.UpdateTotalExecutionTime();
525      this.OnStateLogListChanged();
526
527      if (this.ExecutionState != ExecutionState.Stopped) {
528        this.RefreshAutomatically = true;
529      }
530
531      var handler = Loaded;
532      if (handler != null) handler(this, EventArgs.Empty);
533    }
534
535    public event EventHandler<CollectionItemsChangedEventArgs<HiveTask>> HiveTasksAdded;
536    private void OnHiveTasksAdded(CollectionItemsChangedEventArgs<HiveTask> e) {
537      var handler = HiveTasksAdded;
538      if (handler != null) handler(this, e);
539    }
540
541    public event EventHandler<CollectionItemsChangedEventArgs<HiveTask>> HiveTasksRemoved;
542    private void OnHiveTasksRemoved(CollectionItemsChangedEventArgs<HiveTask> e) {
543      var handler = HiveTasksRemoved;
544      if (handler != null) handler(this, e);
545    }
546
547    public event EventHandler<CollectionItemsChangedEventArgs<HiveTask>> HiveTasksReset;
548    private void OnHiveTasksReset(CollectionItemsChangedEventArgs<HiveTask> e) {
549      var handler = HiveTasksReset;
550      if (handler != null) handler(this, e);
551    }
552
553    public Guid Id {
554      get {
555        if (job == null) return Guid.Empty;
556        return job.Id;
557      }
558      set { job.Id = value; }
559    }
560    public bool Modified {
561      get { return job.Modified; }
562    }
563    public void Store() {
564      job.Store();
565    }
566    public string ItemDescription {
567      get { return job.ItemDescription; }
568    }
569    public Image ItemImage {
570      get { return job.ItemImage; }
571    }
572    public string ItemName {
573      get { return job.ItemName; }
574    }
575    public Version ItemVersion {
576      get { return job.ItemVersion; }
577    }
578
579    public override string ToString() {
580      return string.Format("{0} {1}", Job.DateCreated.ToString("MM.dd.yyyy HH:mm"), Job.ToString());
581    }
582
583    public bool IsFinished() {
584      return HiveTasks != null
585        && HiveTasks.All(x => x.Task.DateFinished.HasValue && x.Task.DateCreated.HasValue);
586    }
587
588    public IEnumerable<HiveTask> GetAllHiveTasks() {
589      if (hiveTasks == null) return Enumerable.Empty<HiveTask>();
590
591      var tasks = new List<HiveTask>();
592      foreach (HiveTask task in HiveTasks) {
593        tasks.AddRange(task.GetAllHiveTasks());
594      }
595      return tasks;
596    }
597
598    public int CompareTo(RefreshableJob other) {
599      return this.ToString().CompareTo(other.ToString());
600    }
601
602    public void Unload() {
603      // stop result polling
604      if (refreshAutomatically)
605        RefreshAutomatically = false;
606      DisposeTasks();
607      hiveTasks = new ItemCollection<HiveTask>();
608    }
609
610    #region IDisposable Members
611    public void Dispose() {
612      if (jobDownloader != null) {
613        jobDownloader.ExceptionOccured -= new EventHandler<EventArgs<Exception>>(jobDownloader_ExceptionOccured);
614        jobDownloader.Dispose();
615        jobDownloader = null;
616      }
617      if (jobResultPoller != null) {
618        DeregisterResultPollingEvents();
619        jobResultPoller = null;
620      }
621      if (hiveTasks != null) {
622        DisposeTasks();
623      }
624      if (job != null) {
625        DeregisterJobEvents();
626        job = null;
627      }
628    }
629
630    private void DisposeTasks() {
631      DeregisterHiveTasksEvents();
632      foreach (var task in hiveTasks) {
633        task.Dispose();
634      }
635      hiveTasks.Clear(); // this should remove the item_StateLogChanged event handlers
636      hiveTasks = null;
637    }
638    #endregion
639  }
640}
Note: See TracBrowser for help on using the repository browser.