public StatsCallable(Action deleg,
                bool logStats,
                IRunningAverageAndStdDev timing,
				AtomicInteger noEstimateCounter) {
    this._Delegate = deleg;
    this.logStats = logStats;
    this.timing = timing;
    this.noEstimateCounter = noEstimateCounter;
  }
  public static LoadStatistics runLoad(IRecommender recommender, int howMany) {
    IDataModel dataModel = recommender.GetDataModel();
    int numUsers = dataModel.GetNumUsers();
    double sampleRate = 1000.0 / numUsers;
    var userSampler =
        SamplinglongPrimitiveIterator.MaybeWrapIterator(dataModel.GetUserIDs(), sampleRate);
    
	if (userSampler.MoveNext()) 
		recommender.Recommend(userSampler.Current, howMany); // Warm up
    
	var callables = new List<Action>();
    while (userSampler.MoveNext()) {
      callables.Add(new LoadCallable(recommender, userSampler.Current).call);
    }
    AtomicInteger noEstimateCounter = new AtomicInteger();
    IRunningAverageAndStdDev timing = new FullRunningAverageAndStdDev();
    AbstractDifferenceRecommenderEvaluator.execute(callables, noEstimateCounter, timing);
    return new LoadStatistics(timing);
  }
  private static List<Action> wrapWithStatsCallables(List<Action> callables,
                                                                   AtomicInteger noEstimateCounter,
                                                                   IRunningAverageAndStdDev timing) {
    List<Action> wrapped = new List<Action>();
    for (int count=0; count<callables.Count; count++) {
      bool logStats = count % 1000 == 0; // log every 1000 or so iterations
	  wrapped.Add(new StatsCallable(callables[count], logStats, timing, noEstimateCounter).call);
    }
    return wrapped;
  }
  public static void execute(List<Action> callables,
                                AtomicInteger noEstimateCounter,
                                IRunningAverageAndStdDev timing) {

    List<Action> wrappedCallables = wrapWithStatsCallables(callables, noEstimateCounter, timing);
    int numProcessors = Environment.ProcessorCount;
    var tasks = new Task[wrappedCallables.Count];
    log.Info("Starting timing of {} tasks in {} threads", wrappedCallables.Count, numProcessors);
    try {
		for (int i=0; i<tasks.Length; i++)
			tasks[i] = Task.Factory.StartNew( wrappedCallables[i] );
		Task.WaitAll( tasks, 10000 ); // 10 sec
      /*List<Future<Void>> futures = executor.invokeAll(wrappedCallables);
      // Go look for exceptions here, really
      for (Future<Void> future : futures) {
        future.get();
      }*/

    } catch (Exception e) {
      throw new TasteException(e.Message, e);
    }
    
    /*executor.shutdown();
    try {
      executor.awaitTermination(10, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
      throw new TasteException(e.getCause());
    }*/
  }
  private double getEvaluation(FastByIDMap<IPreferenceArray> testPrefs, IRecommender recommender)
  {
    reset();
    var estimateCallables = new List<Action>();
    AtomicInteger noEstimateCounter = new AtomicInteger();
    foreach (var entry in testPrefs.EntrySet()) {
      estimateCallables.Add( () => {
		  var testUserID = entry.Key;
		  var prefs = entry.Value;

		  foreach (IPreference realPref in prefs) {
			float estimatedPreference = float.NaN;
			try {
			  estimatedPreference = recommender.EstimatePreference(testUserID, realPref.GetItemID());
			} catch (NoSuchUserException nsue) {
			  // It's possible that an item exists in the test data but not training data in which case
			  // NSEE will be thrown. Just ignore it and move on.
			  log.Info("User exists in test data but not training data: {}", testUserID);
			} catch (NoSuchItemException nsie) {
			  log.Info("Item exists in test data but not training data: {}", realPref.GetItemID());
			}
			if (float.IsNaN(estimatedPreference)) {
			  noEstimateCounter.incrementAndGet();
			} else {
			  estimatedPreference = capEstimatedPreference(estimatedPreference);
			  processOneEstimate(estimatedPreference, realPref);
			}
		  }


	  });
         // new PreferenceEstimateCallable(recommender, entry.Key, entry.Value, noEstimateCounter));
    }
    log.Info("Beginning evaluation of {} users", estimateCallables.Count);
    IRunningAverageAndStdDev timing = new FullRunningAverageAndStdDev();
    execute(estimateCallables, noEstimateCounter, timing);
    return computeFinalEvaluation();
  }