0
votes

I'm running into a situation that's making me think I don't understand async / await mechanics as well as I thought.

I've got a Windows desktop app which is mostly WPF but uses a WinForms host to show a 3rd party COM object. The process of loading the COM object is pretty slow, so I've been trying to move the creation and initialization of those objects to a task to free up the UI while that work happens, but I'm finding that in some situations when the await Task.Run() returns, it's not on the UI thread. As a result, when I change the Visible property on the WinForms host after the task returns it throws because of a cross-thread call.

The calling function looks like this:

public async Task<bool> LoadPreview(string filePath)
{
    bool result;

    try
    {
        await _semaphore.WaitAsync();
        result = await Task.Run(() => CreateAndInitializePreviewer(filePath));

        if (result)
        {
            Visible = false; // <-- occasionally crashes because I'm not on the UI thread
            _currentHandler.DoPreview();
            Visible = true;
        }
    }
    finally
    {
        _semaphore.Release();
    }

    return result;
}

The code inside CreateAndInitializePreviewer does not have any async / await calls. I've verified that before the call to Task.Run() I'm always on the UI thread.

Any suggestions on what I should be looking for that would cause the await Task.Run() to come back to a different thread? Any ideas are appreciated.

1
COM is propably not desigend to work with async. Remember, that it is a predecessor to .NET. And it has many limtiations, that were overcome by .NET. | For example on top of all the other issues, the (t)rusty office COM interop, requires a interactive session. A Serious issue if you want to use it form a Service, including a WebServer. - Christopher
Some good reading: devblogs.microsoft.com/dotnet/configureawait-faq Another thing to remember - ALWAYS dispatch your UI work to main thread. E.g. for WinForms see advice here: docs.microsoft.com/en-us/dotnet/framework/winforms/controls/… - zaitsman
Have you tried setting ConfigureAwait = true? - John Wu
Please review minimal reproducible example guide on posting code and edit the question. 99% the issue is not in the code shown but in the code that calls this method as it occasionally called without synchronization context. - Alexei Levenkov
@JohnWu Isn't true the default? Unless I'm missing something, ConfigureAwait(true) never has any effect. - Gabriel Luci

1 Answers

4
votes

The process of loading the COM object is pretty slow, so I've been trying to move the creation and initialization of those objects to a task to free up the UI while that work happens

This is probably not going to work, unless your COM object is free-threaded (which is unlikely). If you want to push that work off your UI thread, you'll probably need to create a separate STA thread to hold the COM object and marshal calls to/from that thread - meaning all calls to that COM object, since it would live in the other STA thread. It's fairly straightforward in WPF to create a second UI (STA) thread; it's quite a bit harder but still possible in WinForms.

Any suggestions on what I should be looking for that would cause the await Task.Run() to come back to a different thread?

Yes. await will capture SynchronizationContext.Current if it is not null, and it should not be null in this case. It should be either an instance of DispatcherSynchronizationContext (which continues executing by sending a message to the WPF dispatcher) or WinFormsSynchronizationContext (which continues executing by sending a message to the WinForms winproc).

My initial guess is that there's something odd going on with SynchronizationContext.Current due to the WinForms-in-WPF architecture.