0

One of our clients has reported an issue with one of our Add-ins becoming unresponsive. The error reported contains a stack trace that goes deep into the bowels of WPF and I won't distract you with that. Suffice to say that the top level error reads:

Dispatcher processing has been suspended, but messages are still being processed.

The same code works perfectly on my, and as far as I know any other, machine. All the code does is instantiate a WPF window and call its .ShowDialog() method.

Looking through issues with the same error, I find answers such as this: Dispatcher throws InvalidOperationException on Messagebox.Show in Textchanged event or WPF : Dispatcher processing has been suspended, but messages are still being processed

In both cases the answer provided is to use Dispatcher.BeginInvoke

The problem with that is that this call is asynchronous and I need to wait for user response. So I use Dispatcher.Invoke instead:

Dispatcher.Invoke(Sub() oAIC.ShowDialog(), System.Windows.Threading.DispatcherPriority.Normal)

Once again this works perfectly on my machine but that means nothing. The original code worked perfectly on my machine, too.

So my question is:

Is this even worth pursuing? Or is the fact that I use .Invoke instead of .BeginInvoke equivalent to me just using the original .ShowDialog() directly so I'm wasting my time?

Suffice to say that the client in question is not of the cooperative kind. Other clients would be happy for me to give them a trial version and report back, but this person simply wants a solution and doesn't want to be bothered with anything else. As this has never been reported by any other client I can't even find another "guinea pig" to try this on, so all I have left is the option to ask it here in the hope that someone with sufficient expertise can tell me "yes this is a good way forward" or "you're wasting your time". Sorry if this doesn't quite fit the model for asking questions here but I'm at the end of my rope...

further clarification after seeing Peregrine's answer

The code is part of an Office Add-in (in this case for Microsoft Word). The WPF dialog I'm trying to display pops up several levels down from a button click in the Ribbon Bar.

I put the code behind the button click into a function

DoTheButtonCode()

and called it as follows

Await System.Threading.Tasks.Task.Run(AddressOf DoTheButtonCode)

When I tried this, an error was raised:

An exception of type 'System.InvalidOperationException' occurred in PresentationCore.dll but was not handled in user code Additional information: The calling thread must be STA, because many UI components require this.

Now this is the weird thing. In the original Button Click event, the apartmentstate of system.threading.thread.currentthread is STA but when I then step into the DoTheButtonCode and I check system.threading.thread.currentthread its apartmentstate is MTA

further clarification

Ok - tried something else:

Changed DoTheButtonCode to an Async Function:

Private Async Function DoTheButtonCode() As Threading.Tasks.Task(Of Integer)

The button Click event handler method is now defined Private Async Sub, and within it I call

Await DoTheButtonCode()

This actually works, apart from this niggling warning that appears in the function definition of DoTheButtonCode: "this async method lacks 'Await' operators and so will run synchronously" (etc)

So while this does work I suspect that I'm kidding myself here.

I think I'm about ready to give up. I'll just add a try/catch construct around my original ShowDialog call and if the error is raised at least it will simply do nothing rather than crash :(

Thanks for all your help, Peregrine

  • `Task.Run` will use the default scheduler which will schedule onto a thread pool thread, which will not be STA. You can write a custom scheduler which will schedule onto STA threads; I leave it as an exercise for the reader, it should be easy to find examples using the googles. For UI-related things, the bigger issue would be that it would put it on a non-UI thread, so you would need to dispatch back to the UI thread in `DoTheButtonCode` if you want to do UI-related things there. – Craig Aug 28 '19 at 14:38
  • If you want to wait for a user response, you should be able to `Await` a `BeginInvoke` call; in fact, in my experience, you get a compiler error if you *don't* await it (I have warning disable pragmas in my code for the instances where I want to fire and forget a BeginInvoke). – Craig Aug 28 '19 at 14:41
  • Thanks Craig. I'll keep that in mind in case I have to revisit my "blunt instrument" approach as stated at the end of the OP. For now I will just Catch the error, ignore it and do nothing, in the hope that this is a "one off" issue for this particular user and if they click the button a second time it'll be fine as it is for literally everybody else. If that fails, yes, I'll look into creating a custom scheduler but I hope I don't have to :) –  Aug 28 '19 at 16:02
  • I misremembered, you don't even necessarily need to write it, there's a Microsoft parallel extensions extras pack that includes an STA task scheduler. – Craig Aug 28 '19 at 17:41
  • Thanks Craig for the extra clarification. –  Aug 29 '19 at 07:56

1 Answers1

0

I'm doing something very similar in my library for displaying message dialogs in a MVVM pure manner.

The only differences I can see between my code and yours is

  1. I'm using Dispatcher.InvokeAsync() rather than Dispatcher.Invoke() - which makes the call awaitable.

  2. I'm creating my dialog window inside the Dispatcher.BeginInvoke() call - it's not clear where your oAIC window is being created.

private async Task<perDialogButton> _ShowDialogAsync(object viewModel, object content, string title, perDialogButton buttons, perDialogIcon dialogIcon)
{
    // get control associated with ViewModel instance
    var associatedControl = ... 

    return await associatedControl.Dispatcher.InvokeAsync(() =>
    {
        var window = new perDialog
        {
            // set dialog properties 
            ... 
        };

        window.ShowDialog();

        return window.SelectedButton;
    });
}

Mode details, and the full source code on my blog post

Peregrine
  • 4,287
  • 3
  • 17
  • 34
  • Note that you can also [await BeginInvoke](https://stackoverflow.com/a/13413367/1136211). The important difference between BeginInvoke and InvokeAsync is the argument type. – Clemens Aug 28 '19 at 12:07
  • Thank you very much Peregrine, but I think I'm fighting a losing battle here.... I'll post an explanation below by using the Answer Your Question button as I can't include enough detail in a comment..... Bear with me :) –  Aug 28 '19 at 12:31
  • ok it seems like that is not the correct thing to do so I will add further clarification by editing the question. –  Aug 28 '19 at 12:33
  • @Peregrine - I've added the further clarification in my original post. Thanks for your help but I think I've hit the proverbial brick wall. Hopefully this will be of some limited use to someone else. –  Aug 28 '19 at 13:12