source: branches/3.3-HiveMigration/sources/HeuristicLab.Hive/HeuristicLab.Hive.Experiment/3.3/HiveExperiment.cs @ 4139

Last change on this file since 4139 was 4139, checked in by cneumuel, 10 years ago

HiveExperiment now flats the optimizer-tree (supports batch runs and sub-experiments)

File size: 24.9 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2010 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.Linq;
24using HeuristicLab.Common;
25using HeuristicLab.Core;
26using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
27using HeuristicLab.Optimization;
28using System.Drawing;
29using HeuristicLab.Collections;
30using System.Collections.Generic;
31using HeuristicLab.Hive.Contracts.BusinessObjects;
32using System.IO;
33using HeuristicLab.Persistence.Default.Xml;
34using HeuristicLab.PluginInfrastructure;
35using System.Reflection;
36using HeuristicLab.Hive.Contracts.Interfaces;
37using HeuristicLab.Hive.Contracts;
38using System.Threading;
39using HeuristicLab.Tracing;
40using HeuristicLab.Hive.JobBase;
41using System.Diagnostics;
42using System.Collections;
43
44namespace HeuristicLab.Hive.Experiment {
45  /// <summary>
46  /// An experiment which contains multiple batch runs of algorithms.
47  /// </summary>
48  [Item(itemName, itemDescription)]
49  [Creatable("Testing & Analysis")]
50  [StorableClass]
51  public class HiveExperiment : NamedItem, IExecutable {
52    private const string itemName = "Hive Experiment";
53    private const string itemDescription = "A runner for a single experiment, which's algorithms are executed in the Hive.";
54    private const int resultPollingIntervalMs = 15000;
55    private const int maxSnapshotRetries = 20;
56    private object locker = new object();
57
58    private System.Timers.Timer timer;
59    private bool pausePending, stopPending;
60
61    [Storable]
62    private DateTime lastUpdateTime;
63
64    private bool isPollingResults;
65    public bool IsPollingResults {
66      get { return isPollingResults; }
67      private set {
68        if (isPollingResults != value) {
69          isPollingResults = value;
70          OnIsPollingResultsChanged();
71        }
72      }
73    }
74
75    private bool stopResultsPollingPending = false;
76
77    private IDictionary<Guid, Thread> resultPollingThreads;
78
79    /// <summary>
80    /// Mapping from JobId to an optimizer.
81    /// Stores all pending optimizers. If an optimizer is finished it is removed from this collection
82    /// </summary>
83    [Storable]
84    private IDictionary<Guid, IOptimizer> pendingOptimizersByJobId = new Dictionary<Guid, IOptimizer>();
85
86    /// <summary>
87    /// Stores a mapping from the child-optimizer to the parent optimizer.
88    /// Needed to replace a finished optimizer in the optimizer-tree.
89    /// Only pending optmizers are stored.
90    /// </summary>
91    [Storable]
92    private IDictionary<IOptimizer, IOptimizer> parentOptimizersByPendingOptimizer = new Dictionary<IOptimizer, IOptimizer>();
93
94    [Storable]
95    private JobItemList jobItems;
96    public JobItemList JobItems {
97      get { return jobItems; }
98    }
99
100
101    [Storable]
102    private string serverUrl;
103    public string ServerUrl {
104      get { return serverUrl; }
105      set {
106        if (serverUrl != value) {
107          serverUrl = value;
108          OnServerUrlChanged();
109        }
110      }
111    }
112
113    [Storable]
114    private string resourceIds;
115    public string ResourceIds {
116      get { return resourceIds; }
117      set {
118        if (resourceIds != value) {
119          resourceIds = value;
120          OnResourceIdsChanged();
121        }
122      }
123    }
124
125    [Storable]
126    private HeuristicLab.Optimization.Experiment experiment;
127    public HeuristicLab.Optimization.Experiment Experiment {
128      get { return experiment; }
129      set {
130        if (experiment != value) {
131          experiment = value;
132          OnExperimentChanged();
133        }
134      }
135    }
136
137    [Storable]
138    private ILog log;
139    public ILog Log {
140      get { return log; }
141    }
142
143    [StorableConstructor]
144    public HiveExperiment(bool deserializing)
145      : base(deserializing) {
146      this.resultPollingThreads = new Dictionary<Guid, Thread>();
147      jobItems = new JobItemList();
148    }
149
150    public HiveExperiment()
151      : base(itemName, itemDescription) {
152      this.ServerUrl = HeuristicLab.Hive.Experiment.Properties.Settings.Default.HiveServerUrl;
153      this.ResourceIds = HeuristicLab.Hive.Experiment.Properties.Settings.Default.ResourceIds;
154      this.log = new Log();
155      pausePending = stopPending = false;
156      jobItems = new JobItemList();
157      isPollingResults = false;
158      resultPollingThreads = new Dictionary<Guid, Thread>();
159      InitTimer();
160    }
161
162    public override IDeepCloneable Clone(Cloner cloner) {
163      LogMessage("I am beeing cloned");
164      HiveExperiment clone = (HiveExperiment)base.Clone(cloner);
165      clone.resourceIds = this.resourceIds;
166      clone.serverUrl = this.serverUrl;
167      clone.experiment = (HeuristicLab.Optimization.Experiment)cloner.Clone(experiment);
168      clone.executionState = this.executionState;
169      clone.executionTime = this.executionTime;
170      clone.pendingOptimizersByJobId = new Dictionary<Guid, IOptimizer>();
171      foreach (var pair in this.pendingOptimizersByJobId)
172        clone.pendingOptimizersByJobId[pair.Key] = (IOptimizer)cloner.Clone(pair.Value);
173      foreach (var pair in this.parentOptimizersByPendingOptimizer)
174        clone.parentOptimizersByPendingOptimizer[pair.Key] = (IOptimizer)cloner.Clone(pair.Value);
175      clone.log = (ILog)cloner.Clone(log);
176      clone.stopPending = this.stopPending;
177      clone.pausePending = this.pausePending;
178      clone.jobItems = (JobItemList)cloner.Clone(jobItems);
179      clone.lastUpdateTime = this.lastUpdateTime;
180      clone.isPollingResults = this.isPollingResults;
181      return clone;
182    }
183
184    [StorableHook(HookType.AfterDeserialization)]
185    private void AfterDeserialization() {
186      InitTimer();
187      this.IsPollingResults = false;
188      this.stopResultsPollingPending = false;
189      LogMessage("I was deserialized.");
190    }
191
192    private void InitTimer() {
193      timer = new System.Timers.Timer(100);
194      timer.AutoReset = true;
195      timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
196    }
197
198    private void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) {
199      DateTime now = DateTime.Now;
200      ExecutionTime += now - lastUpdateTime;
201      lastUpdateTime = now;
202    }
203
204    public IEnumerable<string> ResourceGroups {
205      get {
206        if (!string.IsNullOrEmpty(resourceIds)) {
207          return resourceIds.Split(';');
208        } else {
209          return new List<string>();
210        }
211      }
212    }
213    #region IExecutable Members
214
215    [Storable]
216    private Core.ExecutionState executionState;
217    public ExecutionState ExecutionState {
218      get { return executionState; }
219      private set {
220        if (executionState != value) {
221          executionState = value;
222          OnExecutionStateChanged();
223        }
224      }
225    }
226
227    [Storable]
228    private TimeSpan executionTime;
229    public TimeSpan ExecutionTime {
230      get { return executionTime; }
231      private set {
232        if (executionTime != value) {
233          executionTime = value;
234          OnExecutionTimeChanged();
235        }
236      }
237    }
238
239    public void Pause() {
240      throw new NotSupportedException();
241    }
242
243    public void Prepare() {
244      if (experiment != null) {
245        experiment.Prepare();
246        this.ExecutionState = Core.ExecutionState.Prepared;
247        OnPrepared();
248      }
249    }
250
251    public void Start() {
252      OnStarted();
253      lastUpdateTime = DateTime.Now;
254      this.ExecutionState = Core.ExecutionState.Started;
255      Thread t = new Thread(() => {
256        IExecutionEngineFacade executionEngineFacade = GetExecutionEngineFacade();
257
258        pendingOptimizersByJobId = new Dictionary<Guid, IOptimizer>();
259        parentOptimizersByPendingOptimizer = GetOptimizers(true);
260        IEnumerable<string> groups = ResourceGroups;
261       
262        foreach (IOptimizer optimizer in parentOptimizersByPendingOptimizer.Keys) {
263          SerializedJob serializedJob = CreateSerializedJob(optimizer);
264          ResponseObject<JobDto> response = executionEngineFacade.AddJobWithGroupStrings(serializedJob, groups);
265          pendingOptimizersByJobId.Add(response.Obj.Id, optimizer);
266
267          JobItem jobItem = new JobItem() {
268            JobDto = response.Obj,
269            LatestSnapshot = new ResponseObject<SerializedJob>() {
270              Obj = serializedJob,
271              StatusMessage = "Initial Snapshot",
272              Success = true
273            }
274          };
275          jobItems.Add(jobItem);
276          jobItem.LogMessage("Job sent to Hive");
277
278          LogMessage("Sent job to Hive (jobId: " + response.Obj.Id + ")");
279        }
280
281        // start results polling after sending sending the jobs to the server (to avoid race conflicts at the optimizers-collection)
282        StartResultPolling();
283      });
284      t.Start();
285    }
286
287    public void Stop() {
288      this.ExecutionState = Core.ExecutionState.Stopped;
289      foreach (JobItem jobItem in jobItems) {
290        AbortJob(jobItem.JobDto.Id);
291      }
292      OnStopped();
293    }
294
295    private void CreateResultPollingThreads() {
296      foreach (JobItem jobItem in JobItems) {
297        if (!resultPollingThreads.ContainsKey(jobItem.JobDto.Id)) {
298          resultPollingThreads.Add(jobItem.JobDto.Id, CreateResultPollingThread(jobItem.JobDto));
299        }
300      }
301    }
302
303    public void StartResultPolling() {
304      this.stopResultsPollingPending = false;
305      CreateResultPollingThreads();
306      foreach (Thread pollingThread in resultPollingThreads.Values) {
307        if (pollingThread.ThreadState != System.Threading.ThreadState.Running) {
308          pollingThread.Start();
309        }
310      }
311      this.IsPollingResults = true;
312    }
313
314    public void StopResultPolling() {
315      this.stopResultsPollingPending = true;
316      foreach (Thread pollingThread in resultPollingThreads.Values) {
317        pollingThread.Interrupt();
318      }
319      this.stopResultsPollingPending = false;
320    }
321
322    private JobItem GetJobItemById(Guid jobId) {
323      return jobItems.Single(x => x.JobDto.Id == jobId);
324    }
325
326    /// <summary>
327    /// Returns all optimizers in the current Experiment
328    /// </summary>
329    /// <param name="flatout">if false only top level optimizers are returned, if true the optimizer-tree is flatted</param>
330    /// <returns></returns>
331    private IDictionary<IOptimizer, IOptimizer> GetOptimizers(bool flatout) {
332      if (!flatout) {
333        var optimizers = new Dictionary<IOptimizer, IOptimizer>();
334        foreach (IOptimizer opt in experiment.Optimizers) {
335          optimizers.Add(experiment, opt);
336        }
337        return optimizers;
338      } else {
339        return FlatOptimizerTree(null, experiment);
340      }
341    }
342
343    /// <summary>
344    /// Recursively iterates all IOptimizers in the optimizer-tree and returns them.
345    ///
346    /// [chn] this could be implemented more cleanly if Experiment and BatchRun would implement an interface like:
347    /// interface IParallelizable {
348    ///   IEnumerable&lt;IOptimizer&gt; GetOptimizers();
349    /// }
350    /// </summary>
351    /// <returns>a dictionary mapping from the parent optimizer to the child optimizer</returns>
352    private IDictionary<IOptimizer, IOptimizer> FlatOptimizerTree(IOptimizer parent, IOptimizer optimizer) {
353      IDictionary<IOptimizer, IOptimizer> optimizers = new Dictionary<IOptimizer, IOptimizer>();
354      if (optimizer is HeuristicLab.Optimization.Experiment) {
355        HeuristicLab.Optimization.Experiment experiment = optimizer as HeuristicLab.Optimization.Experiment;
356        foreach (IOptimizer opt in experiment.Optimizers) {
357          AddRange(optimizers, FlatOptimizerTree(experiment, opt));
358        }
359      } else if (optimizer is BatchRun) {
360        BatchRun batchRun = optimizer as BatchRun;
361        for (int i = 0; i < batchRun.Repetitions; i++) {
362          IOptimizer opt = (IOptimizer)batchRun.Algorithm.Clone();
363          AddRange(optimizers, FlatOptimizerTree(batchRun, opt));
364        }
365      } else if (optimizer is EngineAlgorithm) {
366        optimizers.Add(optimizer, parent);
367      } else {
368        Logger.Warn("Optimizer of type " + optimizers.GetType().ToString() + " unknown");
369        optimizers.Add(optimizer, parent);
370      }
371      return optimizers;
372    }
373
374    private void AddRange(IDictionary<IOptimizer, IOptimizer> optimizers, IDictionary<IOptimizer, IOptimizer> childs) {
375      foreach (KeyValuePair<IOptimizer, IOptimizer> kvp in childs) {
376        optimizers.Add(kvp);
377      }
378    }
379
380    private void ReplaceOptimizer(IOptimizer parentOptimizer, IOptimizer originalOptimizer, IOptimizer newOptimizer) {
381      lock (locker) {
382        if (parentOptimizer is HeuristicLab.Optimization.Experiment) {
383          HeuristicLab.Optimization.Experiment exp = (HeuristicLab.Optimization.Experiment)parentOptimizer;
384          int originalOptimizerIndex = exp.Optimizers.IndexOf(originalOptimizer);
385          exp.Optimizers[originalOptimizerIndex] = newOptimizer;
386        } else if (parentOptimizer is BatchRun) {
387          BatchRun batchRun = (BatchRun)parentOptimizer;
388          if (newOptimizer is IAlgorithm) {
389            batchRun.Runs.Add(new Run((IAlgorithm)newOptimizer));
390          } else {
391            throw new NotSupportedException("Only IAlgorithm types supported");
392          }
393        } else {
394          throw new NotSupportedException("Invalid parentOptimizer");
395        }
396      }
397    }
398
399    public void AbortJob(Guid jobId) {
400      IExecutionEngineFacade executionEngineFacade = GetExecutionEngineFacade();
401      Response response = executionEngineFacade.AbortJob(jobId);
402      GetJobItemById(jobId).LogMessage("Aborting Job: " + response.StatusMessage);
403    }
404
405    #endregion
406
407    private IExecutionEngineFacade GetExecutionEngineFacade() {
408      return ServiceLocator.CreateExecutionEngineFacade(ServerUrl);
409    }
410
411    private SerializedJob CreateSerializedJob(IOptimizer optimizer) {
412      IJob job = new OptimizerJob() {
413        Optimizer = optimizer
414      };
415
416      // serialize job
417      MemoryStream memStream = new MemoryStream();
418      XmlGenerator.Serialize(job, memStream);
419      byte[] jobByteArray = memStream.ToArray();
420      memStream.Dispose();
421
422      // find out which which plugins are needed for the given object
423      List<HivePluginInfoDto> pluginsNeeded = (
424        from p in GetDeclaringPlugins(optimizer.GetType())
425        select new HivePluginInfoDto() {
426          Name = p.Name,
427          Version = p.Version
428        }).ToList();
429
430      JobDto jobDto = new JobDto() {
431        CoresNeeded = 1, // [chn] how to determine real cores needed?
432        PluginsNeeded = pluginsNeeded,
433        State = State.Offline,
434        MemoryNeeded = 0,
435        UserId = Guid.Empty // [chn] set real userid here!
436      };
437
438      SerializedJob serializedJob = new SerializedJob() {
439        JobInfo = jobDto,
440        SerializedJobData = jobByteArray
441      };
442
443      return serializedJob;
444    }
445
446    private Thread CreateResultPollingThread(JobDto job) {
447      return new Thread(() => {
448        try {
449          GetJobItemById(job.Id).LogMessage("Starting job results polling");
450          IExecutionEngineFacade executionEngineFacade = GetExecutionEngineFacade();
451          IJob restoredObject = null;
452
453          do {
454            Thread.Sleep(resultPollingIntervalMs);
455            if (stopPending || !this.IsPollingResults) {
456              return;
457            }
458
459            ResponseObject<JobDto> response = executionEngineFacade.GetJobById(job.Id);
460            LogMessage("Response: " + response.StatusMessage + " (jobId: " + job.Id + ")");
461            GetJobItemById(job.Id).LogMessage("Response: " + response.StatusMessage);
462
463            UpdateJobItem(response.Obj);
464
465            if (response.Obj.State == State.Abort) {
466              // job is aborted, don't poll for results anymore
467              GetJobItemById(job.Id).LogMessage("Job successfully aborted");
468              return;
469            }
470
471            // loop while
472            // 1. the user doesn't request an abort
473            // 2. there is a problem with server communication (success==false)
474            // 3. no result for the job is available yet (response.Obj==null)
475            // 4. the result that we get from the server is a snapshot and not the final result
476            if (response.Success && response.Obj != null && response.Obj.State == State.Finished) {
477              ResponseObject<SerializedJob> jobResponse = executionEngineFacade.GetLastSerializedResult(job.Id, false, false);
478              restoredObject = XmlParser.Deserialize<IJob>(new MemoryStream(jobResponse.Obj.SerializedJobData));
479              UpdateSnapshot(jobResponse);
480            }
481          } while (restoredObject == null || restoredObject.ExecutionState != Core.ExecutionState.Stopped);
482
483          LogMessage("Job finished (jobId: " + job.Id + ")");
484          GetJobItemById(job.Id).LogMessage("Job finished");
485          // job retrieved... replace the existing optimizers with the finished one
486          IOptimizer originalOptimizer = pendingOptimizersByJobId[job.Id];
487          IOptimizer restoredOptimizer = ((OptimizerJob)restoredObject).Optimizer;
488
489          ReplaceOptimizer(parentOptimizersByPendingOptimizer[originalOptimizer], originalOptimizer, restoredOptimizer);
490          pendingOptimizersByJobId.Remove(job.Id);
491          parentOptimizersByPendingOptimizer.Remove(originalOptimizer);
492
493          if (pendingOptimizersByJobId.Count == 0) {
494            // finished
495            this.ExecutionState = Core.ExecutionState.Stopped;
496            OnStopped();
497          }
498        } catch (ThreadInterruptedException exception) {
499
500        } finally {
501          GetJobItemById(job.Id).LogMessage("ResultsPolling Thread stopped");
502          resultPollingThreads.Remove(job.Id);
503          if (resultPollingThreads.Count == 0) {
504            IsPollingResults = false;
505          }
506        }
507      });
508    }
509
510    private void UpdateJobItem(JobDto jobDto) {
511      JobItem jobItem = jobItems.Single(x => x.JobDto.Id == jobDto.Id);
512      jobItem.JobDto = jobDto;
513    }
514
515    private void UpdateSnapshot(ResponseObject<SerializedJob> response) {
516      JobItem jobItem = jobItems.Single(x => x.JobDto.Id == response.Obj.JobInfo.Id);
517      jobItem.LatestSnapshot = response;
518    }
519
520    private void LogMessage(string message) {
521      // HeuristicLab.Log is not Thread-Safe, so lock every call
522      lock (locker) {
523        log.LogMessage(message);
524      }
525    }
526
527    #region Required Plugin Search
528    /// <summary>
529    /// Returns a list of plugins in which the type itself and all members
530    /// of the type are declared. Objectgraph is searched recursively.
531    /// </summary>
532    private IEnumerable<IPluginDescription> GetDeclaringPlugins(Type type) {
533      HashSet<Type> types = new HashSet<Type>();
534      FindTypes(type, types, "HeuristicLab.");
535      return GetDeclaringPlugins(types);
536    }
537
538    /// <summary>
539    /// Returns the plugins (including dependencies) in which the given types are declared
540    /// </summary>
541    private IEnumerable<IPluginDescription> GetDeclaringPlugins(IEnumerable<Type> types) {
542      HashSet<IPluginDescription> plugins = new HashSet<IPluginDescription>();
543      foreach (Type t in types) {
544        FindDeclaringPlugins(ApplicationManager.Manager.GetDeclaringPlugin(t), plugins);
545      }
546      return plugins;
547    }
548
549    /// <summary>
550    /// Finds the dependencies of the given plugin and adds it to the plugins hashset.
551    /// Also searches the dependencies recursively.
552    /// </summary>
553    private void FindDeclaringPlugins(IPluginDescription plugin, HashSet<IPluginDescription> plugins) {
554      if (!plugins.Contains(plugin)) {
555        plugins.Add(plugin);
556        foreach (IPluginDescription dependency in plugin.Dependencies) {
557          FindDeclaringPlugins(dependency, plugins);
558        }
559      }
560    }
561
562    /// <summary>
563    /// Recursively finds all types used in type which are in a namespace which starts with namespaceStart
564    /// Be aware that search is not performed on attributes
565    /// </summary>
566    /// <param name="type">the type to be searched</param>
567    /// <param name="types">found types will be stored there, needed in order to avoid duplicates</param>
568    /// <param name="namespaceStart">only types from namespaces which start with this will be searched and added</param>
569    private void FindTypes(Type type, HashSet<Type> types, string namespaceStart) {
570      if (!types.Contains(type) && type.Namespace.StartsWith(namespaceStart)) {
571        types.Add(type);
572
573        // constructors
574        foreach (ConstructorInfo info in type.GetConstructors()) {
575          foreach (ParameterInfo paramInfo in info.GetParameters()) {
576            FindTypes(paramInfo.ParameterType, types, namespaceStart);
577          }
578        }
579
580        // interfaces
581        foreach (Type t in type.GetInterfaces()) {
582          FindTypes(t, types, namespaceStart);
583        }
584
585        // events
586        foreach (EventInfo info in type.GetEvents()) {
587          FindTypes(info.EventHandlerType, types, namespaceStart);
588          FindTypes(info.DeclaringType, types, namespaceStart);
589        }
590
591        // properties
592        foreach (PropertyInfo info in type.GetProperties()) {
593          FindTypes(info.PropertyType, types, namespaceStart);
594        }
595
596        // fields
597        foreach (FieldInfo info in type.GetFields()) {
598          FindTypes(info.FieldType, types, namespaceStart);
599        }
600
601        // methods
602        foreach (MethodInfo info in type.GetMethods()) {
603          foreach (ParameterInfo paramInfo in info.GetParameters()) {
604            FindTypes(paramInfo.ParameterType, types, namespaceStart);
605          }
606          FindTypes(info.ReturnType, types, namespaceStart);
607        }
608      }
609    }
610    #endregion
611
612    #region Eventhandler
613
614    public event EventHandler ExecutionTimeChanged;
615    private void OnExecutionTimeChanged() {
616      EventHandler handler = ExecutionTimeChanged;
617      if (handler != null) handler(this, EventArgs.Empty);
618    }
619
620    public event EventHandler ExecutionStateChanged;
621    private void OnExecutionStateChanged() {
622      LogMessage("ExecutionState changed to " + executionState.ToString());
623      EventHandler handler = ExecutionStateChanged;
624      if (handler != null) handler(this, EventArgs.Empty);
625    }
626
627    public event EventHandler<EventArgs<Exception>> ExceptionOccurred;
628
629    public event EventHandler Started;
630    private void OnStarted() {
631      LogMessage("Started");
632      timer.Start();
633      EventHandler handler = Started;
634      if (handler != null) handler(this, EventArgs.Empty);
635    }
636
637    public event EventHandler Stopped;
638    private void OnStopped() {
639      timer.Stop();
640      LogMessage("Stopped");
641      EventHandler handler = Stopped;
642      if (handler != null) handler(this, EventArgs.Empty);
643    }
644
645    public event EventHandler Paused;
646    private void OnPaused() {
647      timer.Stop();
648      LogMessage("Paused");
649      EventHandler handler = Paused;
650      if (handler != null) handler(this, EventArgs.Empty);
651    }
652
653    public event EventHandler Prepared;
654    protected virtual void OnPrepared() {
655      LogMessage("Prepared");
656      EventHandler handler = Prepared;
657      if (handler != null) handler(this, EventArgs.Empty);
658    }
659
660    public event EventHandler ResourceIdsChanged;
661    protected virtual void OnResourceIdsChanged() {
662      EventHandler handler = ResourceIdsChanged;
663      if (handler != null) handler(this, EventArgs.Empty);
664    }
665
666    public event EventHandler ExperimentChanged;
667    protected virtual void OnExperimentChanged() {
668      LogMessage("Experiment changed");
669      EventHandler handler = ExperimentChanged;
670      if (handler != null) handler(this, EventArgs.Empty);
671    }
672
673    public event EventHandler ServerUrlChanged;
674    protected virtual void OnServerUrlChanged() {
675      EventHandler handler = ServerUrlChanged;
676      if (handler != null) handler(this, EventArgs.Empty);
677    }
678
679    public event EventHandler IsResultsPollingChanged;
680    private void OnIsPollingResultsChanged() {
681      if (this.IsPollingResults) {
682        LogMessage("Results Polling Started");
683        timer.Start();
684      } else {
685        LogMessage("Results Polling Stopped");
686        timer.Stop();
687      }
688      EventHandler handler = IsResultsPollingChanged;
689      if (handler != null) handler(this, EventArgs.Empty);
690    }
691    #endregion
692  }
693}
Note: See TracBrowser for help on using the repository browser.