Sören Kuklau
Sören Kuklau

Reputation: 19930

Am I using ContinueWith correctly?

I have an application with a startup window (in WPF) which lets the user log in, choose a database, make some other settings, and eventually (upon success) shows a progress bar. When the progress is done, a main window (in WinForms) is shown instead.

To make the progress bar update smoothly, I'm separating as much as I can away from the UI thread. Here's the current approach:

var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
var backgroundScheduler = TaskScheduler.Default;

var connectingTask = Task.Factory.StartNew(() =>
{
    System.Diagnostics.Debug.WriteLine("connecting started");

    // some code that connects to a server; may fail

    System.Diagnostics.Debug.WriteLine("connecting finishing");
}

var connectFailAction = new Action<Task>((t) =>
{
    System.Diagnostics.Debug.WriteLine("connectFail started");

    // let user know that connecting failed, then exit

    System.Diagnostics.Debug.WriteLine("connectFail finishing");
});

var postConnectAction = new Action<Task>((t) =>
{
    System.Diagnostics.Debug.WriteLine("postConnect started");

    // some initial post-connection setup code; runs in UI thread

    System.Diagnostics.Debug.WriteLine("postConnect finishing");
});

var dbLoadingAction = new Action<Task>((t) =>
{
    System.Diagnostics.Debug.WriteLine("dbLoading started");

    // code that loads settings from the database; runs in background

    System.Diagnostics.Debug.WriteLine("dbLoading finishing");
});

var runGuiAction = new Action<Task>((t) =>
{
    System.Diagnostics.Debug.WriteLine("runGui started");

    // set up the main form, show it, hide this startup progress window

    System.Diagnostics.Debug.WriteLine("runGui finishing");
});

connectingTask.ContinueWith(connectFailAction,
                            System.Threading.CancellationToken.None,
                            TaskContinuationOptions.OnlyOnFaulted,
                            uiScheduler);

connectingTask.ContinueWith(postConnectAction,
                            System.Threading.CancellationToken.None,
                            TaskContinuationOptions.OnlyOnRanToCompletion,
                            uiScheduler)
              .ContinueWith(dbLoadingAction,
                            System.Threading.CancellationToken.None,
                            TaskContinuationOptions.None,
                            backgroundScheduler)
              .ContinueWith(runGuiAction,
                            System.Threading.CancellationToken.None,
                            TaskContinuationOptions.None,
                            uiScheduler);

If the connection fails, the task goes straight to connectFailAction.

Otherwise, on the UI thread, postConnectAction is run next, while simultaneously, dbLoadingAction is run in the background. Afterwards, runGuiAction is run.

This mostly works fine, but one odd edge case where it breaks down is CefSharp. Specifically, non-CefSharp portions of the UI are 'stuck' — I can't resize the form, I can't interact with any controls, etc.

Since equivalent code works fine with the WinForms WebBrowser control, I'm guessing it's partially an issue with CefSharp, but part of it may be my incorrect use/understanding of the TPL.

If I change the code as follows, the UI gets stuck during loading (unsurprisingly), but CefSharp inits correctly.

connectingTask.ContinueWith(connectFailAction,
                            System.Threading.CancellationToken.None,
                            TaskContinuationOptions.OnlyOnFaulted,
                            uiScheduler);

connectingTask.Wait();
postConnectAction.Invoke(null);
dbLoadingAction.Invoke(null);
runGuiAction.Invoke(null);

If (and only if) using CefSharp, the main thread is stuck in System.Windows.Threading.DispatcherSynchronizationContext.Wait, so I'm guessing one of the above tasks never cleanly finishes, causing an event queue not to flush properly?

Upvotes: 1

Views: 282

Answers (1)

Yuval Itzchakov
Yuval Itzchakov

Reputation: 149538

The main thread is stuck in System.Windows.Threading.DispatcherSynchronizationContext.Wait, so I'm guessing one of the above tasks never cleanly finishes, causing an event queue not to flush properly?

The problem is your code deadlocks in the latter example. You're invoking the Task which later attempts to run the continuation on the UI thread, which is currently blocked by connectingTask.Wait();. This is why you shouldn't block on async code.

Regarding CefSharp, perhaps the continuation you're running on the UI thread after connecting is CPU intensive and causing your UI message loop to halt for more than a reasonable amount of time. Can't be sure since you haven't provided the relevant code.

Upvotes: 1

Related Questions