Steve Serdy
Steve Serdy

Reputation: 9

Why would await Task.Run() not return to the same thread?

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.

Upvotes: -1

Views: 403

Answers (1)

Stephen Cleary
Stephen Cleary

Reputation: 456687

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.

Upvotes: 3

Related Questions