Reputation: 71
I have a program where the user can view various 3Dmodels which are saved in XAML and then manipulate the Viewport3D using a TrackballDecorator. Included in these XAML files are transforms which I was applying to the TrackballDecorator using the modifications in the answer to the question WPF 3D - How can I save and load a Camera view?
I recently switched the project to the .net 4.5 framework to take advantage of the new async functionality of the XamlReader class and it has caused a conflict with the way I'm accessing the transforms saved with the models.
What I am currently doing is:
private Task<TrackballDecorator> loadModel(string path)
{
var tempVP = new XamlReader().LoadAsync(XmlReader.Create(path)) as Viewport3D;
return new TrackballDecorator() { Content = tempVP, Transform = (tempVP.Camera.Transform as Transform3DGroup) };
}
This doesn't work, as the XamlReader isn't finished loading so a null Transform is returned.
What I would like to do is:
private async Task<TrackballDecorator> loadModelAsync(string path)
{
var tempVP = await new XamlReader().LoadAsync(XmlReader.Create(path)) as Viewport3D;
return new TrackballDecorator() { Content = tempVP, Transform = (tempVP.Camera.Transform as Transform3DGroup) };
}
But because the LoadAsync method returns an object
and not a Task<object>
I cannot use the await keyword. Is there any way to overcome or work around this limitation?
Upvotes: 2
Views: 947
Reputation: 131749
There are two issues here.
On the one hand, the "canonical" way to convert an EAP operation to a Task is to use a TaskCompletionSource and return the source's Task property. The event handler calls SetResult to signal that the Task completed and set its result.
You could write something like this:
public Task<ViewPort3D> LoadViewPortAsync(string path)
{
var tcs=new TaskCompletionSource();
var reader=XmlReader.Create(path);
ViewPort3D port=null;
var xr = new XamlReader();
xr.LoadCompleted += (o,e)=>{
tcs.SetResult(port);
reader.Dispose();
};
port=xr.LoadAsync(reader);
return tcs.Task;
}
Once you get the ViewPort3D object, you can create your transformation
On the other hand, the "canonical" WPF way is to observe objects that change and react to their change. LoadAsync returns the root XAML object and adds nodes to it asynchronously. It is up to ViewPort3D to raise INotifyPropertyChanged as appropriate and it is up to the TrackballDecorator and the Transforms to react to these changes.
Using TaskCompletionSource is just a workaround for the second issue.
Upvotes: 2
Reputation: 43634
You should be able to create a Task and await on that
private async Task<TrackballDecorator> loadModelAsync(string path)
{
var tempVP = await Task.Factory.StartNew<Viewport3D>(() => (Viewport3D)XamlReader.Load(XmlReader.Create(path)));
return new TrackballDecorator() { Content = tempVP, Transform = (tempVP.Camera.Transform as Transform3DGroup) };
}
Upvotes: 1
Reputation: 71
I ended up solving this using the XamlReader LoadCompleted event handler and a SlimSemaphore as seen here: Is it possible to await an event instead of another async method?
private SemaphoreSlim signal = new SemaphoreSlim(0, 1);
private async Task<TrackballDecorator> loadModel(string path) {
XamlReader xr = new XamlReader();
xr.LoadCompleted += new AsyncCompletedEventHandler(AsyncXamlComplete);
var tempVP = xr.LoadAsync(XmlReader.Create(path)) as Viewport3D;
await signal.WaitAsync();
return new TrackballDecorator() { Content = tempVP, Transform = tempVP.Camera.Transform as Transform3DGroup) };
}
private void AsyncXamlComplete(Object sender, AsyncCompletedEventArgs e) {
signal.Release();
}
Upvotes: 1