ExportTileCacheTask.EstimateTileCacheAsync

3430
4
Jump to solution
07-15-2015 03:05 AM
JensBuchta
Occasional Contributor

Currently, I'm testing ExportTileCacheTask.EstimateTileCacheAsync(..). While it's pretty easy to get started with this API, I'm having a hard time to get it really stable and user-friendly. I'm not sure if I'm using it wrong or if anything should be improved behind the scenes.

No detail information, if number of tiles > 100.000

Querying the Sampleserver (World_Street_Map (MapServer)) for the whole world with LOD == 9 results in an Exception with the message "Export tile operation failed".

If I use the REST API, I do find the reason why, it clearly states: "ERROR 001564: Requested tile count(262144) exceeds the maximum allowed number of tiles(100000) to be exported for service World_Street_Map:MapServer".

The only way to get access to this information is to parse all messages of ExportTileCacheJob.Messages after execution finished... which brings to the next problem:

await exportTilesTask.EstimateTileCacheSizeAsync(...) immediately returns

The method call immediately returns to the execution of the method. At this point, the returned job contains close to zero information. I think the method immediately returns because of "submit and query" pattern implemented at server side. The callback "(result, ex)" does not provide me with job information, so right now I extended my method with a "TaskCompletionSource":

var taskCompletionSource = new TaskCompletionSource<EstimateTileCacheSizeResult>();
var job = await exportTilesTask.EstimateTileCacheSizeAsync(options,
     (result, ex) =>
                    {
                        if (ex == null) { taskCompletionSource.SetResult(result); }
                        else { taskCompletionSource.SetException(ex); }
                    }, .....);
await taskCompletionSource.Task;
// job finished with all information here

This works (for me), but I really don't know if that's a correct / good pattern. It seems like a much of overhead to simply get an estimation. Is this pattern correct? Should it be integrated into the SDK itself? Is there a simpler way to get finished job information?

Randomly getting HTTP 500 Errors

I do get random 500 errors, raising an exception and exiting the estimation. I'm not absolutely sure, but I think I do get those errors more often, the smaller I set checkInterval argument.

Maybe, this is what happens behind the scenes:

  1. Runtime SDK asks server to start the job
  2. Server returns the job id, while still starting the job
  3. Runtime SDK asks (kind of) immediately for the job status
  4. Server is still starting the job and replies with 500

Taking the job id of the error message and looking at the state with the Web Browser, the job states "succeeded", so I guess it must be some kind of timing problem (see step 3).

Any ideas on that?

Kind Regards

Jens Buchta

0 Kudos
1 Solution

Accepted Solutions
AnttiKajanus1
Regular Contributor II

Hi,

It seems that the issue is based on the SyncronizationContext. If you just define as below, it doesn't actually set  SynchronizationContext.Current so task crashes to it because it is null.

SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());

The error that we get isn't very informative in this case and this is something that we need to improve for the future.

Here is a helper class to get WPF syncronization context by Stephen Toub

public static class Helper
  {
  public static void RunInWpfSyncContext( Func<Task> function )
  {
   if (function == null) throw new ArgumentNullException("function");
   var prevCtx = SynchronizationContext.Current;
   try
   {
  var syncCtx = new DispatcherSynchronizationContext();
  SynchronizationContext.SetSynchronizationContext(syncCtx);


  var task = function();
  if (task == null) throw new InvalidOperationException();


  var frame = new DispatcherFrame();
  var t2 = task.ContinueWith(x=>{frame.Continue = false;}, TaskScheduler.Default);
  Dispatcher.PushFrame(frame);   // execute all tasks until frame.Continue == false


  task.GetAwaiter().GetResult(); // rethrow exception when task has failed 
   }
   finally
   { 
  SynchronizationContext.SetSynchronizationContext(prevCtx);
   }
  }
  }

And you can run the test like this

private const string MapServerUri = "http://sampleserver6.arcgisonline.com/arcgis/rest/services/World_Street_Map/MapServer";


[TestMethod]
public async Task EstimateTileCacheSize()
{
  // Run test in WPF syncronization context
  Helper.RunInWpfSyncContext(async () =>
  {
  Assert.IsNotNull(SynchronizationContext.Current);


  // Create completation source to see when onCompletedCallback is called
  var tcs = new TaskCompletionSource<Tuple<EstimateTileCacheSizeResult, Exception>>();


  // Create filter
  var filter = Envelope.FromJson(
  "{\"xmin\":-123.96726562499894,\"ymin\":36.737415380601156,\"xmax\":-119.57273437500105,\"ymax\":38.481185065451001,\"spatialReference\":{\"wkid\":4326}}");


  var options = new GenerateTileCacheParameters
  {
  Format = ExportTileCacheFormat.TilePackage,
  GeometryFilter = filter,
  };


  ExportTileCacheTask exportTilesTask = new ExportTileCacheTask(new Uri(MapServerUri));


  // Create job and await that it's submitted ok.
  ExportTileCacheJob estimateJob = await exportTilesTask.EstimateTileCacheSizeAsync(options,
  (result, ex) =>
  {
  tcs.SetResult(Tuple.Create(result, ex));
  },
  TimeSpan.FromSeconds(3),
  CancellationToken.None);


  // Wait until onCompletedCallback is called. This prevents unit test to complete before we get the results.
  var results = await tcs.Task;


  Assert.IsNotNull(results.Item1);
  Assert.IsNull(results.Item2);
  });
}

View solution in original post

0 Kudos
4 Replies
JensBuchta
Occasional Contributor

Could anyone please comment on why this UnitTest code (MSTest Project) fails in "onCompletedCallback"?

The job itself runs fine and I am able to query results using "CheckEstimateTileCacheSizeJobStatusAsync", but "onCompleteCallback" passes a NullReferenceException.

0 Kudos
AnttiKajanus1
Regular Contributor II

Hi,

It seems that the issue is based on the SyncronizationContext. If you just define as below, it doesn't actually set  SynchronizationContext.Current so task crashes to it because it is null.

SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());

The error that we get isn't very informative in this case and this is something that we need to improve for the future.

Here is a helper class to get WPF syncronization context by Stephen Toub

public static class Helper
  {
  public static void RunInWpfSyncContext( Func<Task> function )
  {
   if (function == null) throw new ArgumentNullException("function");
   var prevCtx = SynchronizationContext.Current;
   try
   {
  var syncCtx = new DispatcherSynchronizationContext();
  SynchronizationContext.SetSynchronizationContext(syncCtx);


  var task = function();
  if (task == null) throw new InvalidOperationException();


  var frame = new DispatcherFrame();
  var t2 = task.ContinueWith(x=>{frame.Continue = false;}, TaskScheduler.Default);
  Dispatcher.PushFrame(frame);   // execute all tasks until frame.Continue == false


  task.GetAwaiter().GetResult(); // rethrow exception when task has failed 
   }
   finally
   { 
  SynchronizationContext.SetSynchronizationContext(prevCtx);
   }
  }
  }

And you can run the test like this

private const string MapServerUri = "http://sampleserver6.arcgisonline.com/arcgis/rest/services/World_Street_Map/MapServer";


[TestMethod]
public async Task EstimateTileCacheSize()
{
  // Run test in WPF syncronization context
  Helper.RunInWpfSyncContext(async () =>
  {
  Assert.IsNotNull(SynchronizationContext.Current);


  // Create completation source to see when onCompletedCallback is called
  var tcs = new TaskCompletionSource<Tuple<EstimateTileCacheSizeResult, Exception>>();


  // Create filter
  var filter = Envelope.FromJson(
  "{\"xmin\":-123.96726562499894,\"ymin\":36.737415380601156,\"xmax\":-119.57273437500105,\"ymax\":38.481185065451001,\"spatialReference\":{\"wkid\":4326}}");


  var options = new GenerateTileCacheParameters
  {
  Format = ExportTileCacheFormat.TilePackage,
  GeometryFilter = filter,
  };


  ExportTileCacheTask exportTilesTask = new ExportTileCacheTask(new Uri(MapServerUri));


  // Create job and await that it's submitted ok.
  ExportTileCacheJob estimateJob = await exportTilesTask.EstimateTileCacheSizeAsync(options,
  (result, ex) =>
  {
  tcs.SetResult(Tuple.Create(result, ex));
  },
  TimeSpan.FromSeconds(3),
  CancellationToken.None);


  // Wait until onCompletedCallback is called. This prevents unit test to complete before we get the results.
  var results = await tcs.Task;


  Assert.IsNotNull(results.Item1);
  Assert.IsNull(results.Item2);
  });
}
0 Kudos
JensBuchta
Occasional Contributor

Antti,

thanks a lot for your reply!

Using your Helper-Class, I am able to unit test the class right now, great!

Just for reference, if other users come across this, here is some detail information about the helper class:

http://blogs.msdn.com/b/pfxteam/archive/2012/01/20/10259049.aspx

http://blogs.msdn.com/b/pfxteam/archive/2012/01/21/10259307.aspx

Kind Regards

Jens

0 Kudos
AnttiKajanus1
Regular Contributor II

Good to know. Can you mark this thread answered, please?

0 Kudos