source: trunk/sources/HeuristicLab.Clients.Hive/3.3/RefreshableJob.cs @ 14185

Last change on this file since 14185 was 14185, checked in by swagner, 3 years ago

#2526: Updated year of copyrights in license headers

File size: 23.5 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2016 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 IProgress progress;
160    public IProgress 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.progress = new Progress();
181      this.refreshAutomatically = false;
182      this.Job = new Job();
183      this.log = new ThreadSafeLog();
184      this.jobDownloader = new ConcurrentTaskDownloader<ItemTask>(Settings.Default.MaxParallelDownloads, Settings.Default.MaxParallelDownloads);
185      this.jobDownloader.ExceptionOccured += new EventHandler<EventArgs<Exception>>(jobDownloader_ExceptionOccured);
186      this.HiveTasks = new ItemCollection<HiveTask>();
187    }
188    public RefreshableJob(Job hiveJob) {
189      this.progress = new Progress();
190      this.refreshAutomatically = true;
191      this.Job = hiveJob;
192      this.log = new ThreadSafeLog();
193      this.jobDownloader = new ConcurrentTaskDownloader<ItemTask>(Settings.Default.MaxParallelDownloads, Settings.Default.MaxParallelDownloads);
194      this.jobDownloader.ExceptionOccured += new EventHandler<EventArgs<Exception>>(jobDownloader_ExceptionOccured);
195      this.HiveTasks = new ItemCollection<HiveTask>();
196    }
197    protected RefreshableJob(RefreshableJob original, Cloner cloner) {
198      cloner.RegisterClonedObject(original, this);
199      this.Job = cloner.Clone(original.Job);
200      this.IsControllable = original.IsControllable;
201      this.log = cloner.Clone(original.log);
202      this.RefreshAutomatically = false; // do not start results polling automatically
203      this.jobDownloader = new ConcurrentTaskDownloader<ItemTask>(Settings.Default.MaxParallelDownloads, Settings.Default.MaxParallelDownloads);
204      this.jobDownloader.ExceptionOccured += new EventHandler<EventArgs<Exception>>(jobDownloader_ExceptionOccured);
205      this.HiveTasks = cloner.Clone(original.HiveTasks);
206      this.ExecutionTime = original.ExecutionTime;
207      this.ExecutionState = original.ExecutionState;
208    }
209    public IDeepCloneable Clone(Cloner cloner) {
210      return new RefreshableJob(this, cloner);
211    }
212    public object Clone() {
213      return this.Clone(new Cloner());
214    }
215    #endregion
216
217    #region JobResultPoller Events
218    public void StartResultPolling() {
219      if (jobResultPoller == null) {
220        jobResultPoller = new JobResultPoller(job.Id, Settings.Default.ResultPollingInterval);
221        RegisterResultPollingEvents();
222        jobResultPoller.AutoResumeOnException = false;
223      }
224
225      if (!jobResultPoller.IsPolling) {
226        jobResultPoller.Start();
227      }
228    }
229
230    public void StopResultPolling() {
231      if (jobResultPoller != null && jobResultPoller.IsPolling) {
232        refreshAutomatically = false;
233        jobResultPoller.Stop();
234        DeregisterResultPollingEvents();
235        jobResultPoller = null;
236      }
237    }
238
239    public void PauseResultPolling() {
240      if (jobResultPoller != null && jobResultPoller.IsPolling) {
241        jobResultPoller.Stop();
242      }
243    }
244
245    private void RegisterResultPollingEvents() {
246      jobResultPoller.ExceptionOccured += new EventHandler<EventArgs<Exception>>(jobResultPoller_ExceptionOccured);
247      jobResultPoller.JobResultsReceived += new EventHandler<EventArgs<IEnumerable<LightweightTask>>>(jobResultPoller_JobResultReceived);
248      jobResultPoller.IsPollingChanged += new EventHandler(jobResultPoller_IsPollingChanged);
249    }
250    private void DeregisterResultPollingEvents() {
251      jobResultPoller.ExceptionOccured -= new EventHandler<EventArgs<Exception>>(jobResultPoller_ExceptionOccured);
252      jobResultPoller.JobResultsReceived -= new EventHandler<EventArgs<IEnumerable<LightweightTask>>>(jobResultPoller_JobResultReceived);
253      jobResultPoller.IsPollingChanged -= new EventHandler(jobResultPoller_IsPollingChanged);
254    }
255    private void jobResultPoller_IsPollingChanged(object sender, EventArgs e) {
256      if (this.refreshAutomatically != jobResultPoller.IsPolling) {
257        this.refreshAutomatically = jobResultPoller.IsPolling;
258        OnRefreshAutomaticallyChanged();
259      }
260    }
261
262    private void jobResultPoller_JobResultReceived(object sender, EventArgs<IEnumerable<LightweightTask>> e) {
263      lock (jobResultReceivedLocker) {
264        foreach (LightweightTask lightweightTask in e.Value) {
265          HiveTask hiveTask = GetHiveTaskById(lightweightTask.Id);
266          if (hiveTask != null) {
267            // lastJobDataUpdate equals DateTime.MinValue right after it was uploaded. When the first results are polled, this value is updated
268            if (hiveTask.Task.State == TaskState.Offline && lightweightTask.State == TaskState.Waiting) {
269              hiveTask.Task.LastTaskDataUpdate = lightweightTask.LastTaskDataUpdate;
270            }
271
272            hiveTask.UpdateFromLightweightJob(lightweightTask);
273
274            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)) {
275              log.LogMessage(string.Format("Downloading task {0}", lightweightTask.Id));
276              hiveTask.IsDownloading = true;
277              jobDownloader.DownloadTaskData(hiveTask.Task, (localJob, itemJob) => {
278                lock (downloadFinishedLocker) {
279                  log.LogMessage(string.Format("Finished downloading task {0}", localJob.Id));
280                  HiveTask localHiveTask = GetHiveTaskById(localJob.Id);
281
282                  if (itemJob == null) {
283                    // something bad happened to this task. bad task, BAAAD task!
284                    localHiveTask.IsDownloading = false;
285                  } else {
286                    // if the task is paused, download but don't integrate into parent optimizer (to avoid Prepare)
287                    if (localJob.State == TaskState.Paused) {
288                      localHiveTask.ItemTask = itemJob;
289                    } else {
290                      if (localJob.ParentTaskId.HasValue) {
291                        HiveTask parentHiveTask = GetHiveTaskById(localJob.ParentTaskId.Value);
292                        parentHiveTask.IntegrateChild(itemJob, localJob.Id);
293                      } else {
294                        localHiveTask.ItemTask = itemJob;
295                      }
296                    }
297                    localHiveTask.IsDownloading = false;
298                    localHiveTask.Task.LastTaskDataUpdate = lightweightTask.LastTaskDataUpdate;
299                  }
300                }
301              });
302            }
303          }
304        }
305        GC.Collect(); // force GC, because .NET is too lazy here (deserialization takes a lot of memory)
306        if (AllJobsFinished()) {
307          this.ExecutionState = Core.ExecutionState.Stopped;
308          StopResultPolling();
309        }
310        UpdateTotalExecutionTime();
311        UpdateStatistics();
312        OnStateLogListChanged();
313        OnTaskReceived();
314      }
315    }
316
317    public HiveTask GetHiveTaskById(Guid jobId) {
318      foreach (HiveTask t in this.HiveTasks) {
319        var hj = t.GetHiveTaskByTaskId(jobId);
320        if (hj != null)
321          return hj;
322      }
323      return null;
324    }
325
326    private void UpdateStatistics() {
327      var jobs = this.GetAllHiveTasks();
328      job.JobCount = jobs.Count();
329      job.CalculatingCount = jobs.Count(j => j.Task.State == TaskState.Calculating);
330      job.FinishedCount = jobs.Count(j => j.Task.State == TaskState.Finished);
331      OnJobStatisticsChanged();
332    }
333
334    public bool AllJobsFinished() {
335      return this.GetAllHiveTasks().All(j => (j.Task.State == TaskState.Finished
336                                                   || j.Task.State == TaskState.Aborted
337                                                   || j.Task.State == TaskState.Failed)
338                                                   && !j.IsDownloading);
339    }
340
341    private void jobResultPoller_ExceptionOccured(object sender, EventArgs<Exception> e) {
342      OnExceptionOccured(sender, e.Value);
343    }
344    private void jobDownloader_ExceptionOccured(object sender, EventArgs<Exception> e) {
345      OnExceptionOccured(sender, e.Value);
346    }
347    public void UpdateTotalExecutionTime() {
348      this.ExecutionTime = TimeSpan.FromMilliseconds(this.GetAllHiveTasks().Sum(x => x.Task.ExecutionTime.TotalMilliseconds));
349    }
350    #endregion
351
352    #region Job Events
353    private void RegisterJobEvents() {
354      job.ToStringChanged += new EventHandler(OnToStringChanged);
355      job.PropertyChanged += new PropertyChangedEventHandler(job_PropertyChanged);
356      job.ItemImageChanged += new EventHandler(job_ItemImageChanged);
357      job.ModifiedChanged += new EventHandler(job_ModifiedChanged);
358    }
359
360    private void DeregisterJobEvents() {
361      job.ToStringChanged -= new EventHandler(OnToStringChanged);
362      job.PropertyChanged -= new PropertyChangedEventHandler(job_PropertyChanged);
363      job.ItemImageChanged -= new EventHandler(job_ItemImageChanged);
364      job.ModifiedChanged -= new EventHandler(job_ModifiedChanged);
365    }
366    #endregion
367
368    #region Event Handler
369    public event EventHandler RefreshAutomaticallyChanged;
370    private void OnRefreshAutomaticallyChanged() {
371      var handler = RefreshAutomaticallyChanged;
372      if (handler != null) handler(this, EventArgs.Empty);
373    }
374
375    public event EventHandler JobChanged;
376    private void OnJobChanged() {
377      var handler = JobChanged;
378      if (handler != null) handler(this, EventArgs.Empty);
379    }
380
381    public event EventHandler ModifiedChanged;
382    private void job_ModifiedChanged(object sender, EventArgs e) {
383      var handler = ModifiedChanged;
384      if (handler != null) handler(sender, e);
385    }
386
387    public event EventHandler ItemImageChanged;
388    private void job_ItemImageChanged(object sender, EventArgs e) {
389      var handler = ItemImageChanged;
390      if (handler != null) handler(this, e);
391    }
392
393    public event PropertyChangedEventHandler PropertyChanged;
394    private void job_PropertyChanged(object sender, PropertyChangedEventArgs e) {
395      this.IsSharable = job.Permission == Permission.Full;
396      this.IsControllable = job.Permission == Permission.Full;
397
398      var handler = PropertyChanged;
399      if (handler != null) handler(sender, e);
400    }
401
402    public event EventHandler ToStringChanged;
403    private void OnToStringChanged(object sender, EventArgs e) {
404      var handler = ToStringChanged;
405      if (handler != null) handler(this, e);
406    }
407
408    public event EventHandler IsDownloadableChanged;
409    private void OnIsDownloadableChanged() {
410      var handler = IsDownloadableChanged;
411      if (handler != null) handler(this, EventArgs.Empty);
412    }
413
414    public event EventHandler IsControllableChanged;
415    private void OnIsControllableChanged() {
416      var handler = IsControllableChanged;
417      if (handler != null) handler(this, EventArgs.Empty);
418    }
419
420    public event EventHandler IsSharableChanged;
421    private void OnIsSharableChanged() {
422      var handler = IsSharableChanged;
423      if (handler != null) handler(this, EventArgs.Empty);
424    }
425
426    public event EventHandler JobStatisticsChanged;
427    private void OnJobStatisticsChanged() {
428      var handler = JobStatisticsChanged;
429      if (handler != null) handler(this, EventArgs.Empty);
430    }
431
432    public event EventHandler<EventArgs<Exception>> ExceptionOccured;
433    private void OnExceptionOccured(object sender, Exception exception) {
434      log.LogException(exception);
435      var handler = ExceptionOccured;
436      if (handler != null) handler(sender, new EventArgs<Exception>(exception));
437    }
438
439    public event EventHandler StateLogListChanged;
440    private void OnStateLogListChanged() {
441      var handler = StateLogListChanged;
442      if (handler != null) handler(this, EventArgs.Empty);
443    }
444
445    public event EventHandler ExecutionTimeChanged;
446    protected virtual void OnExecutionTimeChanged() {
447      var handler = ExecutionTimeChanged;
448      if (handler != null) handler(this, EventArgs.Empty);
449    }
450
451    public event EventHandler ExecutionStateChanged;
452    protected virtual void OnExecutionStateChanged() {
453      var handler = ExecutionStateChanged;
454      if (handler != null) handler(this, EventArgs.Empty);
455    }
456    public event EventHandler TaskReceived;
457    protected virtual void OnTaskReceived() {
458      var handler = TaskReceived;
459      if (handler != null) handler(this, EventArgs.Empty);
460    }
461    public event EventHandler IsProgressingChanged;
462    private void OnIsProgressingChanged() {
463      var handler = IsProgressingChanged;
464      if (handler != null) handler(this, EventArgs.Empty);
465    }
466    #endregion
467
468    #region HiveTasks Events
469    private void RegisterHiveTasksEvents() {
470      this.hiveTasks.ItemsAdded += new CollectionItemsChangedEventHandler<HiveTask>(hivetasks_ItemsAdded);
471      this.hiveTasks.ItemsRemoved += new CollectionItemsChangedEventHandler<HiveTask>(hiveTasks_ItemsRemoved);
472      this.hiveTasks.CollectionReset += new CollectionItemsChangedEventHandler<HiveTask>(hiveTasks_CollectionReset);
473    }
474
475    private void DeregisterHiveTasksEvents() {
476      this.hiveTasks.ItemsAdded -= new CollectionItemsChangedEventHandler<HiveTask>(hivetasks_ItemsAdded);
477      this.hiveTasks.ItemsRemoved -= new CollectionItemsChangedEventHandler<HiveTask>(hiveTasks_ItemsRemoved);
478      this.hiveTasks.CollectionReset -= new CollectionItemsChangedEventHandler<HiveTask>(hiveTasks_CollectionReset);
479    }
480
481    private void hiveTasks_CollectionReset(object sender, CollectionItemsChangedEventArgs<HiveTask> e) {
482      foreach (var item in e.Items) {
483        item.StateLogChanged -= new EventHandler(item_StateLogChanged);
484      }
485      OnHiveTasksReset(e);
486    }
487
488    private void hiveTasks_ItemsRemoved(object sender, CollectionItemsChangedEventArgs<HiveTask> e) {
489      foreach (var item in e.Items) {
490        item.StateLogChanged -= new EventHandler(item_StateLogChanged);
491      }
492      OnHiveTasksRemoved(e);
493    }
494
495    private void hivetasks_ItemsAdded(object sender, CollectionItemsChangedEventArgs<HiveTask> e) {
496      foreach (var item in e.Items) {
497        item.StateLogChanged += new EventHandler(item_StateLogChanged);
498        item.IsControllable = this.IsControllable;
499      }
500      OnHiveTasksAdded(e);
501    }
502
503    private void item_StateLogChanged(object sender, EventArgs e) {
504      OnStateLogListChanged();
505    }
506    #endregion
507
508    public event EventHandler HiveTasksChanged;
509    protected virtual void OnHiveTasksChanged() {
510      if (this.HiveTasks != null && this.HiveTasks.Count > 0 && this.GetAllHiveTasks().All(x => x.Task.Id != Guid.Empty)) {
511        if (IsFinished()) {
512          this.ExecutionState = Core.ExecutionState.Stopped;
513          this.RefreshAutomatically = false;
514          StopResultPolling();
515        } else {
516          this.RefreshAutomatically = true;
517        }
518      }
519
520      var handler = HiveTasksChanged;
521      if (handler != null) handler(this, EventArgs.Empty);
522    }
523
524    public event EventHandler Loaded;
525    public virtual void OnLoaded() {
526      this.UpdateTotalExecutionTime();
527      this.OnStateLogListChanged();
528
529      if (this.ExecutionState != ExecutionState.Stopped) {
530        this.RefreshAutomatically = true;
531      }
532
533      var handler = Loaded;
534      if (handler != null) handler(this, EventArgs.Empty);
535    }
536
537    public event EventHandler<CollectionItemsChangedEventArgs<HiveTask>> HiveTasksAdded;
538    private void OnHiveTasksAdded(CollectionItemsChangedEventArgs<HiveTask> e) {
539      var handler = HiveTasksAdded;
540      if (handler != null) handler(this, e);
541    }
542
543    public event EventHandler<CollectionItemsChangedEventArgs<HiveTask>> HiveTasksRemoved;
544    private void OnHiveTasksRemoved(CollectionItemsChangedEventArgs<HiveTask> e) {
545      var handler = HiveTasksRemoved;
546      if (handler != null) handler(this, e);
547    }
548
549    public event EventHandler<CollectionItemsChangedEventArgs<HiveTask>> HiveTasksReset;
550    private void OnHiveTasksReset(CollectionItemsChangedEventArgs<HiveTask> e) {
551      var handler = HiveTasksReset;
552      if (handler != null) handler(this, e);
553    }
554
555    public Guid Id {
556      get {
557        if (job == null) return Guid.Empty;
558        return job.Id;
559      }
560      set { job.Id = value; }
561    }
562    public bool Modified {
563      get { return job.Modified; }
564    }
565    public void Store() {
566      job.Store();
567    }
568    public string ItemDescription {
569      get { return job.ItemDescription; }
570    }
571    public Image ItemImage {
572      get { return job.ItemImage; }
573    }
574    public string ItemName {
575      get { return job.ItemName; }
576    }
577    public Version ItemVersion {
578      get { return job.ItemVersion; }
579    }
580
581    public override string ToString() {
582      return string.Format("{0} {1}", Job.DateCreated.ToString("MM.dd.yyyy HH:mm"), Job.ToString());
583    }
584
585    public bool IsFinished() {
586      return HiveTasks != null
587        && HiveTasks.All(x => x.Task.DateFinished.HasValue && x.Task.DateCreated.HasValue);
588    }
589
590    public IEnumerable<HiveTask> GetAllHiveTasks() {
591      if (hiveTasks == null) return Enumerable.Empty<HiveTask>();
592
593      var tasks = new List<HiveTask>();
594      foreach (HiveTask task in HiveTasks) {
595        tasks.AddRange(task.GetAllHiveTasks());
596      }
597      return tasks;
598    }
599
600    public int CompareTo(RefreshableJob other) {
601      return this.ToString().CompareTo(other.ToString());
602    }
603
604    public void Unload() {
605      // stop result polling
606      if (refreshAutomatically)
607        RefreshAutomatically = false;
608      DisposeTasks();
609      hiveTasks = new ItemCollection<HiveTask>();
610    }
611
612    #region IDisposable Members
613    public void Dispose() {
614      if (jobDownloader != null) {
615        jobDownloader.ExceptionOccured -= new EventHandler<EventArgs<Exception>>(jobDownloader_ExceptionOccured);
616        jobDownloader.Dispose();
617        jobDownloader = null;
618      }
619      if (jobResultPoller != null) {
620        DeregisterResultPollingEvents();
621        jobResultPoller = null;
622      }
623      if (hiveTasks != null) {
624        DisposeTasks();
625      }
626      if (job != null) {
627        DeregisterJobEvents();
628        job = null;
629      }
630    }
631
632    private void DisposeTasks() {
633      DeregisterHiveTasksEvents();
634      foreach (var task in hiveTasks) {
635        task.Dispose();
636      }
637      hiveTasks.Clear(); // this should remove the item_StateLogChanged event handlers
638      hiveTasks = null;
639    }
640    #endregion
641  }
642}
Note: See TracBrowser for help on using the repository browser.