Free cookie consent management tool by TermsFeed Policy Generator

source: branches/HeuristicLab.Hive/sources/HeuristicLab.Hive.New/HeuristicLab.Clients.Hive/3.3/HiveExperiment/HiveExperimentClient.cs @ 4649

Last change on this file since 4649 was 4649, checked in by cneumuel, 13 years ago
  • moved db-context into datalayer
  • businesslayer only defines the transaction-scope
  • removed contextfactory
  • implemented convert-methods
  • made naming in db and domainobjects more consistent
  • changed wcf-service to be http-only (for now)

(#1233)

File size: 22.7 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.Collections.Generic;
24using System.Linq;
25using System.Threading;
26using HeuristicLab.Common;
27using HeuristicLab.Core;
28using HeuristicLab.Tracing;
29using HeuristicLab.Services.Hive.Common;
30using HeuristicLab.Services.Hive.Common.ServiceContracts;
31
32namespace HeuristicLab.Clients.Hive {
33  using DT = HeuristicLab.Services.Hive.Common.DataTransfer;
34  using HeuristicLab.Services.Hive.Common.DataTransfer;
35  using HeuristicLab.Clients.Hive.Jobs;
36
37  /// <summary>
38  /// An experiment which contains multiple batch runs of algorithms.
39  /// </summary>
40  [Item(itemName, itemDescription)]
41  public class HiveExperimentClient : NamedItem, IExecutable, IProgressReporter {
42    private object locker = new object();
43    private const string itemName = "Hive Experiment";
44    private const string itemDescription = "A runner for a single experiment, which's algorithms are executed in the Hive.";
45    private System.Timers.Timer timer;
46
47    private Guid hiveExperimentId;
48    public Guid HiveExperimentId {
49      get { return hiveExperimentId; }
50      set { hiveExperimentId = value; }
51    }
52
53    private HiveJob hiveJob;
54    public HiveJob HiveJob {
55      get { return hiveJob; }
56      set {
57        DeregisterHiveJobEvents();
58        if (hiveJob != value) {
59          hiveJob = value;
60          RegisterHiveJobEvents();
61          OnHiveJobChanged();
62        }
63      }
64    }
65
66    private ILog log;
67    public ILog Log {
68      get { return log; }
69    }
70
71    private DateTime lastUpdateTime;
72
73    private string resourceIds;
74    public string ResourceIds {
75      get { return resourceIds; }
76      set {
77        if (resourceIds != value) {
78          resourceIds = value;
79          OnResourceIdsChanged();
80        }
81      }
82    }
83
84    private Guid? rootJobId;
85
86    private bool isPollingResults;
87    public bool IsPollingResults {
88      get { return isPollingResults; }
89      private set {
90        if (isPollingResults != value) {
91          isPollingResults = value;
92          OnIsPollingResultsChanged();
93        }
94      }
95    }
96
97    private bool isProgressing;
98    public bool IsProgressing {
99      get { return isProgressing; }
100      set {
101        if (isProgressing != value) {
102          isProgressing = value;
103          OnIsProgressingChanged();
104        }
105      }
106    }
107
108    private IProgress progress;
109    public IProgress Progress {
110      get { return progress; }
111    }
112
113    private JobResultPoller jobResultPoller;
114
115    public HiveExperimentClient()
116      : base(itemName, itemDescription) {
117      //this.ResourceIds = HeuristicLab.Hive.Experiment.Properties.Settings.Default.ResourceIds;
118      this.ResourceIds = "MyGroup";
119      this.log = new Log();
120      InitTimer();
121    }
122
123    public HiveExperimentClient(DT.HiveExperiment hiveExperimentDto)
124      : this() {
125      UpdateFromDto(hiveExperimentDto);
126    }
127
128    public void UpdateFromDto(DT.HiveExperiment hiveExperimentDto) {
129      this.HiveExperimentId = hiveExperimentDto.Id;
130      this.Name = hiveExperimentDto.Name;
131      this.Description = hiveExperimentDto.Description;
132      //this.ResourceIds = hiveExperimentDto.ResourceIds;
133      this.rootJobId = hiveExperimentDto.RootJobId;
134    }
135
136    public DT.HiveExperiment ToHiveExperimentDto() {
137      return new DT.HiveExperiment() {
138        Id = this.HiveExperimentId,
139        Name = this.Name,
140        Description = this.Description,
141        //ResourceIds = this.ResourceIds,
142        RootJobId = this.rootJobId
143      };
144    }
145
146    public override IDeepCloneable Clone(Cloner cloner) {
147      LogMessage("I am beeing cloned");
148      HiveExperimentClient clone = (HiveExperimentClient)base.Clone(cloner);
149      clone.resourceIds = this.resourceIds;
150      clone.executionState = this.executionState;
151      clone.executionTime = this.executionTime;
152      clone.log = (ILog)cloner.Clone(log);
153      clone.lastUpdateTime = this.lastUpdateTime;
154      clone.rootJobId = this.rootJobId;
155      return clone;
156    }
157
158    public void SetExperiment(Optimization.Experiment experiment) {
159      this.HiveJob = new HiveJob(experiment);
160      Prepare();
161    }
162
163    private void RegisterHiveJobEvents() {
164      if (HiveJob != null) {
165        HiveJob.JobStateChanged += new EventHandler(HiveJob_JobStateChanged);
166      }
167    }
168
169    private void DeregisterHiveJobEvents() {
170      if (HiveJob != null) {
171        HiveJob.JobStateChanged -= new EventHandler(HiveJob_JobStateChanged);
172      }
173    }
174
175    /// <summary>
176    /// Returns the experiment from the root HiveJob
177    /// </summary>
178    public Optimization.Experiment GetExperiment() {
179      if (this.HiveJob != null) {
180        return HiveJob.OptimizerJob.OptimizerAsExperiment;
181      }
182      return null;
183    }
184
185    #region IExecutable Members
186    private Core.ExecutionState executionState;
187    public ExecutionState ExecutionState {
188      get { return executionState; }
189      private set {
190        if (executionState != value) {
191          executionState = value;
192          OnExecutionStateChanged();
193        }
194      }
195    }
196
197    private TimeSpan executionTime;
198    public TimeSpan ExecutionTime {
199      get { return executionTime; }
200      private set {
201        if (executionTime != value) {
202          executionTime = value;
203          OnExecutionTimeChanged();
204        }
205      }
206    }
207
208    public void Pause() {
209      throw new NotSupportedException();
210    }
211
212    public void Prepare() {
213      // do nothing
214    }
215
216    public void Start() {
217      OnStarted();
218      ExecutionTime = new TimeSpan();
219      lastUpdateTime = DateTime.Now;
220      this.ExecutionState = Core.ExecutionState.Started;
221
222      Thread t = new Thread(RunUploadExperiment);
223      t.Name = "RunUploadExperimentThread";
224      t.Start();
225    }
226
227    private void RunUploadExperiment() {
228      try {
229        this.progress = new Progress("Connecting to server...");
230        IsProgressing = true;
231        using (Disposable<IHiveService> service = ServiceLocator.Instance.ServicePool.GetService()) {
232          IEnumerable<string> groups = ToResourceIdList(this.ResourceIds);
233          this.HiveJob.SetIndexInParentOptimizerList(null);
234
235          int totalJobCount = this.HiveJob.GetAllHiveJobs().Count();
236          int jobCount = 0;
237
238          this.progress.Status = "Uploading jobs...";
239          UploadJobWithChildren(service.Obj, this.HiveJob, null, groups, ref jobCount, totalJobCount);
240          this.rootJobId = this.HiveJob.Job.Id;
241          LogMessage("Finished sending jobs to hive");
242
243          // insert or update HiveExperiment
244          this.progress.Status = "Uploading HiveExperiment...";
245
246          DT.HiveExperiment he = service.Obj.GetHiveExperiment(service.Obj.AddHiveExperiment(this.ToHiveExperimentDto()));
247          this.UpdateFromDto(he);
248
249          StartResultPolling();
250        }
251      }
252      catch (Exception e) {
253        OnExceptionOccured(e);
254      }
255      finally {
256        IsProgressing = false;
257      }
258    }
259
260    /// <summary>
261    /// Uploads the given job and all its child-jobs while setting the proper parentJobId values for the childs
262    ///
263    /// </summary>
264    /// <param name="service"></param>
265    /// <param name="hiveJob"></param>
266    /// <param name="parentHiveJob">shall be null if its the root job</param>
267    /// <param name="groups"></param>
268    private void UploadJobWithChildren(IHiveService service, HiveJob hiveJob, HiveJob parentHiveJob, IEnumerable<string> groups, ref int jobCount, int totalJobCount) {
269      jobCount++;
270      this.progress.Status = string.Format("Serializing job {0} of {1}", jobCount, totalJobCount);
271      JobData serializedJob;
272      if (hiveJob.OptimizerJob.ComputeInParallel &&
273        (hiveJob.OptimizerJob.Optimizer is Optimization.Experiment || hiveJob.OptimizerJob.Optimizer is Optimization.BatchRun)) {
274        hiveJob.Job.JobState = JobState.WaitingForChildJobs;
275        hiveJob.OptimizerJob.CollectChildJobs = false; // don't collect child-jobs on slaves
276        serializedJob = hiveJob.GetAsSerializedJob(true);
277      } else {
278        serializedJob = hiveJob.GetAsSerializedJob(false);
279      }
280
281      this.progress.Status = string.Format("Uploading job {0} of {1} ({2} kb)", jobCount, totalJobCount, serializedJob.Data.Count() / 1024);
282      this.progress.ProgressValue = (double)jobCount / totalJobCount;
283
284      if (parentHiveJob != null) {
285        //response = service.AddChildJob(parentHiveJob.Job.Id, serializedJob);
286        hiveJob.Job.Id = service.AddChildJob(parentHiveJob.Job.Id, hiveJob.Job, serializedJob);
287      } else {
288        // response = service.AddJobWithGroupStrings(serializedJob, groups);
289        hiveJob.Job.Id = service.AddJob(hiveJob.Job, serializedJob);
290      }
291
292      LogMessage(hiveJob.Job.Id, "Job sent to Hive");
293
294      foreach (HiveJob child in hiveJob.ChildHiveJobs) {
295        UploadJobWithChildren(service, child, hiveJob, groups, ref jobCount, totalJobCount);
296      }
297    }
298
299    /// <summary>
300    /// Converts a string which can contain Ids separated by ';' to a enumerable
301    /// </summary>
302    private IEnumerable<string> ToResourceIdList(string resourceGroups) {
303      if (!string.IsNullOrEmpty(resourceGroups)) {
304        return resourceIds.Split(';');
305      } else {
306        return new List<string>();
307      }
308    }
309
310    public void Stop() {
311      using (Disposable<IHiveService> service = ServiceLocator.Instance.ServicePool.GetService()) {
312        foreach (HiveJob hj in HiveJob.GetAllHiveJobs()) {
313          service.Obj.AbortJob(hj.Job.Id);
314        }
315      }
316    }
317
318    #endregion
319
320    public void StartResultPolling() {
321      if (!jobResultPoller.IsPolling) {
322        jobResultPoller.Start();
323      } else {
324        throw new JobResultPollingException("Result polling already running");
325      }
326    }
327
328    public void StopResultPolling() {
329      if (jobResultPoller.IsPolling) {
330        jobResultPoller.Stop();
331      } else {
332        throw new JobResultPollingException("Result polling not running");
333      }
334    }
335
336    #region HiveJob Events
337    void HiveJob_JobStateChanged(object sender, EventArgs e) {
338      if (HiveJob != null) {
339        rootJobId = HiveJob.Job.Id;
340      }
341    }
342    #endregion
343
344    #region Eventhandler
345
346    public event EventHandler ExecutionTimeChanged;
347    private void OnExecutionTimeChanged() {
348      EventHandler handler = ExecutionTimeChanged;
349      if (handler != null) handler(this, EventArgs.Empty);
350    }
351
352    public event EventHandler ExecutionStateChanged;
353    private void OnExecutionStateChanged() {
354      LogMessage("ExecutionState changed to " + executionState.ToString());
355      EventHandler handler = ExecutionStateChanged;
356      if (handler != null) handler(this, EventArgs.Empty);
357    }
358
359    public event EventHandler Started;
360    private void OnStarted() {
361      LogMessage("Started");
362      timer.Start();
363      EventHandler handler = Started;
364      if (handler != null) handler(this, EventArgs.Empty);
365    }
366
367    public event EventHandler Stopped;
368    private void OnStopped() {
369      LogMessage("Stopped");
370      timer.Stop();
371      EventHandler handler = Stopped;
372      if (handler != null) handler(this, EventArgs.Empty);
373    }
374
375    public event EventHandler Paused;
376    private void OnPaused() {
377      LogMessage("Paused");
378      EventHandler handler = Paused;
379      if (handler != null) handler(this, EventArgs.Empty);
380    }
381
382    public event EventHandler Prepared;
383    protected virtual void OnPrepared() {
384      LogMessage("Prepared");
385      EventHandler handler = Prepared;
386      if (handler != null) handler(this, EventArgs.Empty);
387    }
388
389    public event EventHandler ResourceIdsChanged;
390    protected virtual void OnResourceIdsChanged() {
391      EventHandler handler = ResourceIdsChanged;
392      if (handler != null) handler(this, EventArgs.Empty);
393    }
394
395    public event EventHandler IsResultsPollingChanged;
396    private void OnIsPollingResultsChanged() {
397      if (this.IsPollingResults) {
398        LogMessage("Results Polling Started");
399      } else {
400        LogMessage("Results Polling Stopped");
401      }
402      EventHandler handler = IsResultsPollingChanged;
403      if (handler != null) handler(this, EventArgs.Empty);
404    }
405
406    public event EventHandler<EventArgs<Exception>> ExceptionOccurred;
407    private void OnExceptionOccured(Exception e) {
408      var handler = ExceptionOccurred;
409      if (handler != null) handler(this, new EventArgs<Exception>(e));
410    }
411
412    public event EventHandler HiveJobChanged;
413    private void OnHiveJobChanged() {
414      if (jobResultPoller != null && jobResultPoller.IsPolling) {
415        jobResultPoller.Stop();
416        DeregisterResultPollingEvents();
417      }
418      if (HiveJob != null) {
419        jobResultPoller = new JobResultPoller(HiveJob, ApplicationConstants.RESULT_POLLING_INTERVAL);
420        RegisterResultPollingEvents();
421      }
422      EventHandler handler = HiveJobChanged;
423      if (handler != null) handler(this, EventArgs.Empty);
424    }
425
426    public event EventHandler IsProgressingChanged;
427    private void OnIsProgressingChanged() {
428      var handler = IsProgressingChanged;
429      if (handler != null) handler(this, EventArgs.Empty);
430    }
431    #endregion
432
433    #region JobResultPoller Events
434    private void RegisterResultPollingEvents() {
435      jobResultPoller.ExceptionOccured += new EventHandler<EventArgs<Exception>>(jobResultPoller_ExceptionOccured);
436      jobResultPoller.JobResultsReceived += new EventHandler<EventArgs<IEnumerable<LightweightJob>>>(jobResultPoller_JobResultReceived);
437      jobResultPoller.PollingStarted += new EventHandler(jobResultPoller_PollingStarted);
438      jobResultPoller.PollingFinished += new EventHandler(jobResultPoller_PollingFinished);
439      jobResultPoller.IsPollingChanged += new EventHandler(jobResultPoller_IsPollingChanged);
440    }
441    private void DeregisterResultPollingEvents() {
442      jobResultPoller.ExceptionOccured -= new EventHandler<EventArgs<Exception>>(jobResultPoller_ExceptionOccured);
443      jobResultPoller.JobResultsReceived -= new EventHandler<EventArgs<IEnumerable<LightweightJob>>>(jobResultPoller_JobResultReceived);
444      jobResultPoller.PollingStarted -= new EventHandler(jobResultPoller_PollingStarted);
445      jobResultPoller.PollingFinished -= new EventHandler(jobResultPoller_PollingFinished);
446      jobResultPoller.IsPollingChanged -= new EventHandler(jobResultPoller_IsPollingChanged);
447    }
448    void jobResultPoller_IsPollingChanged(object sender, EventArgs e) {
449      this.IsPollingResults = jobResultPoller.IsPolling;
450    }
451    void jobResultPoller_PollingFinished(object sender, EventArgs e) {
452      LogMessage("Polling results finished");
453    }
454    void jobResultPoller_PollingStarted(object sender, EventArgs e) {
455      LogMessage("Polling results started");
456    }
457    void jobResultPoller_JobResultReceived(object sender, EventArgs<IEnumerable<LightweightJob>> e) {
458      foreach (LightweightJob lightweightJob in e.Value) {
459        HiveJob hj = hiveJob.GetHiveJobByJobId(lightweightJob.Id);
460        if (hj != null) {
461          hj.UpdateFromLightweightJob(lightweightJob);
462          if ((hj.Job.JobState == JobState.Aborted ||
463               hj.Job.JobState == JobState.Failed ||
464               hj.Job.JobState == JobState.Finished) &&
465              !hj.IsFinishedOptimizerDownloaded) {
466            LogMessage(hj.Job.Id, "Downloading optimizer for job");
467            OptimizerJob optimizerJob = LoadOptimizerJob(hj.Job.Id);
468            if (lightweightJob.ParentJobId.HasValue) {
469              HiveJob parentHiveJob = HiveJob.GetHiveJobByJobId(lightweightJob.ParentJobId.Value);
470              parentHiveJob.UpdateChildOptimizer(optimizerJob, hj.Job.Id);
471            } else {
472              this.HiveJob.IsFinishedOptimizerDownloaded = true;
473            }
474          }
475        }
476      }
477      GC.Collect(); // force GC, because .NET is too lazy here (deserialization takes a lot of memory)
478      if (AllJobsFinished()) {
479        this.ExecutionState = Core.ExecutionState.Stopped;
480        StopResultPolling();
481        OnStopped();
482      }
483    }
484
485    private bool AllJobsFinished() {
486      return HiveJob.GetAllHiveJobs().All(hj => hj.IsFinishedOptimizerDownloaded);
487    }
488
489    void jobResultPoller_ExceptionOccured(object sender, EventArgs<Exception> e) {
490      OnExceptionOccured(e.Value);
491    }
492    #endregion
493
494    #region Execution Time Timer
495    private void InitTimer() {
496      timer = new System.Timers.Timer(100);
497      timer.AutoReset = true;
498      timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
499    }
500
501    private void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) {
502      DateTime now = DateTime.Now;
503      ExecutionTime += now - lastUpdateTime;
504      lastUpdateTime = now;
505    }
506    #endregion
507
508    #region Logging
509    private void LogMessage(string message) {
510      // HeuristicLab.Log is not Thread-Safe, so lock on every call
511      lock (locker) {
512        log.LogMessage(message);
513        Logger.Debug(message);
514      }
515    }
516
517    private void LogMessage(Guid jobId, string message) {
518      //GetJobItemById(jobId).LogMessage(message);
519      LogMessage(message + " (jobId: " + jobId + ")");
520    }
521
522    #endregion
523
524    /// <summary>
525    /// Downloads the root job from hive and sets the experiment, rootJob and rootJobItem
526    /// </summary>
527    public void LoadHiveJob() {
528      progress = new Progress();
529      try {
530        IsProgressing = true;
531        int totalJobCount = 0;
532        int jobCount = 0;
533        progress.Status = "Connecting to Server...";
534        using (var service = ServiceLocator.Instance.ServicePool.GetService()) {
535          // fetch all Job objects to create the full tree of tree of HiveJob objects
536          progress.Status = "Downloading list of jobs...";
537          IEnumerable<LightweightJob> allResults = service.Obj.GetLightweightChildJobs(rootJobId.Value, true, true);
538          totalJobCount = allResults.Count();
539
540          // download them first
541          IDictionary<Guid, Job> allJobs = new Dictionary<Guid, Job>();
542          IDictionary<Guid, JobData> allSerializedJobs = new Dictionary<Guid, JobData>();
543          foreach (LightweightJob lightweightJob in allResults) {
544            jobCount++;
545            progress.Status = string.Format("Downloading {0} of {1} jobs...", jobCount, totalJobCount);
546            allJobs.Add(lightweightJob.Id, service.Obj.GetJob(lightweightJob.Id));
547            allSerializedJobs.Add(lightweightJob.Id, service.Obj.GetJobData(lightweightJob.Id));
548            progress.ProgressValue = (double)jobCount / totalJobCount;
549          }
550
551          jobCount = 1;
552          progress.Status = string.Format("Deserializing {0} of {1} jobs... ({2} kb)", jobCount, totalJobCount, allSerializedJobs[this.rootJobId.Value].Data.Count() / 1024);
553          this.HiveJob = new HiveJob(allJobs[this.rootJobId.Value], allSerializedJobs[this.rootJobId.Value], false);
554          allSerializedJobs.Remove(this.rootJobId.Value); // reduce memory footprint
555          allJobs.Remove(this.rootJobId.Value);
556          progress.ProgressValue = (double)jobCount / totalJobCount;
557
558          if (this.HiveJob.Job.DateFinished.HasValue) {
559            this.ExecutionTime = this.HiveJob.Job.DateFinished.Value - this.HiveJob.Job.DateCreated.Value;
560            this.lastUpdateTime = this.HiveJob.Job.DateFinished.Value;
561            this.ExecutionState = Core.ExecutionState.Stopped;
562            OnStopped();
563          } else {
564            this.ExecutionTime = DateTime.Now - this.HiveJob.Job.DateCreated.Value;
565            this.lastUpdateTime = DateTime.Now;
566            this.ExecutionState = Core.ExecutionState.Started;
567            OnStarted();
568          }
569
570          // build child-job tree
571          LoadChildResults(service.Obj, this.HiveJob, allResults, allJobs, allSerializedJobs, progress, totalJobCount, ref jobCount);
572          StartResultPolling();
573        }
574      }
575      catch (Exception e) {
576        OnExceptionOccured(e);
577      }
578      finally {
579        IsProgressing = false;
580      }
581    }
582
583    private void LoadChildResults(IHiveService service, HiveJob parentHiveJob, IEnumerable<LightweightJob> allResults, IDictionary<Guid, Job> allJobs, IDictionary<Guid, JobData> allSerializedJobs, IProgress progress, int totalJobCount, ref int jobCount) {
584      IEnumerable<LightweightJob> childResults = from result in allResults
585                                                 where result.ParentJobId.HasValue && result.ParentJobId.Value == parentHiveJob.Job.Id
586                                                 orderby result.DateCreated ascending
587                                                 select result;
588      foreach (LightweightJob lightweightJob in childResults) {
589        jobCount++;
590        progress.Status = string.Format("Deserializing {0} of {1} jobs ({2} kb)...", jobCount, totalJobCount, allSerializedJobs[lightweightJob.Id].Data.Count() / 1024);
591        OptimizerJob optimizerJob = PersistenceUtil.Deserialize<OptimizerJob>(allSerializedJobs[lightweightJob.Id].Data);
592        progress.ProgressValue = (double)jobCount / totalJobCount;
593        HiveJob childHiveJob = new HiveJob(optimizerJob, false);
594        parentHiveJob.AddChildHiveJob(childHiveJob);
595        childHiveJob.Job = allJobs[lightweightJob.Id];
596        allSerializedJobs.Remove(lightweightJob.Id); // reduce memory footprint
597        allJobs.Remove(lightweightJob.Id);
598        if (jobCount % 10 == 0) GC.Collect(); // this is needed or otherwise HL takes over the system when the number of jobs is high
599        LoadChildResults(service, childHiveJob, allResults, allJobs, allSerializedJobs, progress, totalJobCount, ref jobCount);
600      }
601    }
602
603    private OptimizerJob LoadOptimizerJob(Guid jobId) {
604      using (var service = ServiceLocator.Instance.ServicePool.GetService()) {
605        JobData serializedJob = service.Obj.GetJobData(jobId);
606        return PersistenceUtil.Deserialize<OptimizerJob>(serializedJob.Data);
607      }
608    }
609  }
610}
Note: See TracBrowser for help on using the repository browser.