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:
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
Solved! Go to Solution.
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); }); }
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); }); }
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
Good to know. Can you mark this thread answered, please?