none
[UWP] Win2D : Loading resources outside of CreateResources does not work RRS feed

  • Question

  • In my app I am using the Win2D CanvasAnimatedControl. The resources are dynamiccaly loaded following the article Loading resources outside of CreateResources.  The reply below contains a stripped down version of the sample code in the article. I can not get it to work to load an image. The problem seems to be in the GameLoopSynchronizationContext where the Task does get in the IsCompleted state. See below.



    Saturday, July 13, 2019 1:02 PM

All replies

  • I tested a stripped down version of the article in a new default blank UWP app where I added a CanvasAnimatedControl to the MainPage.xaml and with the code behind is as below. I deleted the locking code because one cannot await inside a lock.

    The problem is that the Task returned by the LoadResourcesForLevelAsync method does not get to the IsCompleted state, and therefore we can not determine if the task is completed. If the CanvasBitmap.LoadAsync is commented out, the IsCompleted is set to true.

    namespace Win2dTest
    {
        /// <summary>
        /// An empty page that can be used on its own or navigated to within a Frame.
        /// </summary>
        public sealed partial class MainPage : Page
        {
            public MainPage()
            {
                this.InitializeComponent();
    
                canvasAnimatedControl.CreateResources += CanvasAnimatedControl_CreateResources;
                canvasAnimatedControl.Update += CanvasAnimatedControl_Update;
                canvasAnimatedControl.Draw += CanvasAnimatedControl_Draw;
            }
    
            private string imageFilePath = @"Assets\StoreLogo.png";
            private CanvasBitmap canvasBitmap;
    
            private void CanvasAnimatedControl_CreateResources(Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedControl sender, Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesEventArgs args)
            {
                args.TrackAsyncAction(CreateResourcesAsync(sender).AsAsyncAction());
            }
    
            private void CanvasAnimatedControl_Update(Microsoft.Graphics.Canvas.UI.Xaml.ICanvasAnimatedControl sender, Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedUpdateEventArgs args)
            {
                // Check if there is already an outstanding level-loading Task.
                // If so, don't try to spin up a new one.
                bool beginLoad = levelLoadTask == null && needToLoad;
                needToLoad = false;
    
                if (beginLoad)
                {
                    levelLoadTask = LoadResourcesForLevelAsync(sender);
                }
    
                // Indicates the loading task was run and just finished.
                if (levelLoadTask != null && levelLoadTask.IsCompleted)
                {
                    AggregateException levelLoadException = levelLoadTask.Exception;
                    levelLoadTask = null;
    
                    // Query the load task results and re-throw any exceptions
                    // so Win2D can see them. This implements requirement #2.
                    if (levelLoadException != null)
                    {
                        // .NET async tasks wrap all errors in an AggregateException.
                        // We unpack this so Win2D can directly see any lost device errors.
                        levelLoadException.Handle(exception => { throw exception; });
                    }
                }
            }
    
            private void CanvasAnimatedControl_Draw(Microsoft.Graphics.Canvas.UI.Xaml.ICanvasAnimatedControl sender, Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedDrawEventArgs args)
            {
                if (IsLoadInProgress())
                {
                    args.DrawingSession.Clear(Colors.Red);
                }
                else
                {
                    args.DrawingSession.Clear(Colors.Green);
                    if (canvasBitmap != null)
                    {
                        args.DrawingSession.DrawImage(canvasBitmap);
                    }
                }
            }
    
            Task LoadResourcesForLevelAsync(ICanvasAnimatedControl canvasAnimatedControl)
            {
                return GameLoopSynchronizationContext.RunOnGameLoopThreadAsync(canvasAnimatedControl, async () =>
                {
                    StorageFile imageFile = await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFileAsync(imageFilePath);
                    canvasBitmap = await CanvasBitmap.LoadAsync(canvasAnimatedControl, imageFile.Path);
    
                    await Task.CompletedTask;
                });
            }
    
            // Shared state between all threads, and a lock to control access to it.
            bool needToLoad;
            Task levelLoadTask; // This implements requirement #1.
    
            void LoadNewLevel()
            {
                needToLoad = true;
            }
    
            async Task CreateResourcesAsync(CanvasAnimatedControl sender)
            {
                // If there is a previous load in progress, stop it, and
                // swallow any stale errors. This implements requirement #3.
                if (levelLoadTask != null)
                {
                    levelLoadTask.AsAsyncAction().Cancel();
                    try { await levelLoadTask; } catch { }
                    levelLoadTask = null;
                }
    
                // Unload resources used by the previous level here.
    
                // If we are already in a level, reload its per-level resources.
                // This implements requirement #4.
                LoadNewLevel();
            }
    
            bool IsLoadInProgress()
            {
                return levelLoadTask != null;
            }
        }
    
        class GameLoopSynchronizationContext : SynchronizationContext
        {
            ICanvasAnimatedControl control;
    
    
            // Constructor.
            public GameLoopSynchronizationContext(ICanvasAnimatedControl control)
            {
                this.control = control;
            }
    
    
            // Posts a single atomic action for asynchronous execution on the game loop thread.
            public override void Post(SendOrPostCallback callback, object state)
            {
                var action = control.RunOnGameLoopThreadAsync(() =>
                {
                    // Re-register ourselves as the current synchronization context,
                    // to work around CLR issues where this state can sometimes get nulled out.
                    SynchronizationContext.SetSynchronizationContext(this);
    
                    callback(state);
                });
            }
    
    
            // Runs an action, which could contain an arbitrarily complex chain of async awaits,
            // on the game loop thread. This helper registers a custom synchronization context
            // to make sure every await continuation in the chain remains on the game loop
            // thread, regardless of which thread the lower level async operations complete on.
            // It wraps the entire chain with a TaskCompletionSource in order to return a single
            // Task that will be signalled only when the whole chain has completed.
            public static async Task RunOnGameLoopThreadAsync(ICanvasAnimatedControl control, Func<Task> callback)
            {
                var completedSignal = new TaskCompletionSource<object>();
    
                await control.RunOnGameLoopThreadAsync(async () =>
                {
                    try
                    {
                        SynchronizationContext.SetSynchronizationContext(new GameLoopSynchronizationContext(control));
    
                        await callback();
    
                        completedSignal.SetResult(null);
                    }
                    catch (Exception e)
                    {
                        completedSignal.SetException(e);
                    }
                });
    
                await completedSignal.Task;
            }
        };
    }
    

    Saturday, July 13, 2019 6:29 PM
  • Hi,

    I noticed that you had asked about this in the GitHub's issues. That's the right channel to report Win2D issues so let's take this offline temporarily and it will be appreciated if you can update your post here later if you could get solution from GitHub.

    Best regards,

    Roy


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Monday, July 15, 2019 6:55 AM
    Moderator
  • You are correct, here is the link: Github issue

    I created a sample GitHub repository with the code above for easy reproduction and included a link to it in this issue. 

    Will update here when more info on issue.

    Wednesday, July 17, 2019 7:44 AM
  • Hi,

    Sure and it seems that there is some one had replied you in the GitHub. Please check that.

    Best regards,

    Roy


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Thursday, July 18, 2019 2:17 AM
    Moderator