Free cookie consent management tool by TermsFeed Policy Generator

source: branches/HeuristicLab.Hive-3.4/sources/HeuristicLab.Clients.Hive/3.4/HiveExperiment/HiveExperimentClient.cs @ 5053

Last change on this file since 5053 was 5053, checked in by cneumuel, 13 years ago

#1233

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