0

I have a C# UWP desktop application class that I am having issues with when trying to run code in another method that needs to access the UI thread. I have read a few other questions/answers already and tried some solutions, but so far nothing has worked.

I have a method called from a UI event that is accessing the UI okay. Within this, I am calling an awaited method (I need to do this for recursion and to perform some async file processing, not shown here) that will update the UI after individual items are processed. The problem I am running into is mainly a marshalling exception (System.Exception: The application called an interface that was marshalled for a different thread.), although some errors have cropped up from other things I have tried.

Initially, this was not an awaited async method, but I realized it needed to be based on how it behaved in the UI. Before I changed anything, the method was able to access the UI, even without any changes. So, I changed from "IUInit_ProcessDroppedItem(item);" to "await Task.Run(() => IUInit_ProcessDroppedItem(item));" -- Task.Run was needed because I could not compile otherwise since I could not await a void method, according to this). At this point, I started running into the marshalling exception.

Here is the current code for the initial UI event (a drag and drop operation) -

private async void IUInit_DragDropGrid_Drop(object sender, DragEventArgs e)
{
    InitGlobalStatusMessage.Text = "Some text";
    InitMessagesPanel.Children.Clear();

    if (e.DataView.Contains(StandardDataFormats.StorageItems))
    {
        var items = await e.DataView.GetStorageItemsAsync();
        foreach (IStorageItem item in items)
        {
            try
            {
                await Task.Run(() => IUInit_ProcessDroppedItem(item));
            } catch { }
        }
    } 
}

And here was my original code from the method that is called (IUInit_ProcessDroppedItem(item)). The marshalling exception occurs after creating the new TextBlock():

private async void IUInit_ProcessDroppedItem(IStorageItem item)
{
    try
    {
        // For more context, here is some of the code running in the function, not directly relevant.
    if (item.IsOfType(StorageItemTypes.Folder))
        {
            TextBlock msg = new TextBlock();
                msg.Foreground = new SolidColorBrush(Colors.LightYellow);
                msg.Text = "Folder processing : " + item.Name;
                InitMessagesPanel.Children.Add(msg);

            // Get a count of items in this folder (only recurse if there are items)
            StorageFolder folder = (StorageFolder)item;
            IReadOnlyList<IStorageItem> itemsList = await folder.GetItemsAsync();

            if (itemsList.Count==0)
            {
                TextBlock msg2 = new TextBlock();
                    msg2.Foreground = new SolidColorBrush(Colors.LightYellow);
                    msg2.Text = "  Nothing found in this folder (skipped)" + item.Name;
                    InitMessagesPanel.Children.Add(msg);
            } else
            {
                for (int j = 0; j <= itemsList.Count; j++)
                {
                    try
                    {
                        IUInit_ProcessDroppedItem(itemsList[j]);
                    }
                    catch (Exception ex)  {  }
                }
            }
        }
    }
    catch (Exception ex)
    {
            TextBlock msg = new TextBlock();
            msg.Foreground = new SolidColorBrush(Colors.OrangeRed);
            msg.Text = "Processing failed" + item.Name;
            InitMessagesPanel.Children.Add(msg);
    }           
}

There are a couple objects and variables from the XAML code and other code in the page code-behind, but it wasnt relevant to this so I edited it out.

My first attempted fix was to use the Window.Dispatcher fix suggested here. This didn't compile (An object reference is required for the non-static field, method, or property 'Window.Dispatcher')

var ignored = Window.Dispatcher.DispRunAsync(CoreDispatcherPriority.Normal, () =>
    {
        TextBlock msg = new TextBlock();
        msg.Foreground = new SolidColorBrush(Colors.OrangeRed);
        msg.Text = "Processing failed" + item.Name;
        InitMessagesPanel.Children.Add(msg);
    });

I then tweaked to use Window.Current, but this produced a NullReferenceException. From what I read, UWP desktop applications always have this as null.

var ignored = Window.Current.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
    {
        TextBlock msg = new TextBlock();
        msg.Foreground = new SolidColorBrush(Colors.OrangeRed);
        msg.Text = "Processing failed" + item.Name;
        InitMessagesPanel.Children.Add(msg);
    });

Since this I have not been able to get this to work - does anyone know a way to properly access the UI from another method, or a simpler way to do this? It is not an option for me to do everything from the original UI method since the changes need to take place with each recursion. It is unclear to me if the more recent issues have actually fixed the marshalling issue and these are new issues, or the marshalling issue would still be present after fixing the new issues, but either way I am not sure exactly how to correct this.

Mark L
  • 83
  • 7
  • **before I changed anything, the method was able to access the UI, even without any changes** I don't get it. Why change the code which used to work correctly? `IUInit_ProcessDroppedItem` should be a synchronous method and you don't need use await for it. – Ax1le Mar 01 '23 at 08:06
  • I needed an async method because I need the recursive method to print to the UI and there are also some async calls within that for processing the files that I didn't show here. I can put an edit in there. – Mark L Mar 01 '23 at 20:09

1 Answers1

1

I found a solution that resulted in me not needing to have this question answered, but I can share what I have found.

Firstly, I learned from here that I was using the wrong reference for UWP. What I should have been using was Windows.UI.Core:

await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, 
                ()=> 
                {
                    //this part is being executed back on the UI thread...
                });

Even with this fix however, I was still having issues, as the additional async calls within my method were throwing errors (await operator can only be used within a lambda expression) and I ended up not being able to resolve that.

However, the only reason I was running my task with Task.Run in the first place was because of the issue I mentioned before (

Task.Run was needed because I could not compile otherwise since I could not await a void method

)

I changed my method signature from void to Task and that surprisingly resolved almost all my issues. All my awaited methods within as well as my calls to the UI were now working as expected!

In short, the important parts of my code looked like this (I stripped some of the unrelated context I had provided in my initial question):

private async void IUInit_DragDropGrid_Drop(object sender, DragEventArgs e)
{
    if (e.DataView.Contains(StandardDataFormats.StorageItems))
    {
        var items = await e.DataView.GetStorageItemsAsync();
        if (items.Count > 0)
        {
            foreach (IStorageItem item in items)
            {
                await IUInit_ProcessDroppedItem(item);
            }
        }
    }
}

and...

private async Task IUInit_ProcessDroppedItem(IStorageItem item)
{
    await bitmapImage.SetSourceAsync(await wimg.file.OpenAsync(Windows.Storage.FileAccessMode.Read));
}
Mark L
  • 83
  • 7