2

Our team has been having an issue where ContinueWith() won't run until the host process is shutting down. For example, the code below has been running just fine in production for a year. But since we've started using newer versions of our framework, we've had this issue.

When running this code, this.Sites = GetViewableSites(); runs and then nothing else happens when debugging. After hitting F10, the debugger returns to the UI. Only after the user closes the UI does the ContinueWith() execute.

this.PerformBusyAction(() =>
{
    this.Sites = GetViewableSites();
}, ViewModelResource.LoadingForm)
.ContinueWith(originatingTask =>
{
    // ** This doesn't execute until the user closes the UI **
    if (originatingTask.Exception == null)
    {
        PerformPostSuccessfulInitialization();
        return;
    }
    PerformPostUnsuccessfulInitialization(originatingTask.Exception.InnerExceptions);
});

For completeness, here is PerformBusyAction():

    protected Task PerformBusyAction(Action action, string busyMessage)
    {
        this.BusyMessage = busyMessage;
        this.IsBusy = true;  // Let the UI know we're busy, so it can display a busy indicator

        var task = Task.Factory.StartNew(() =>
        {
            try
            {
                action();
            }
            finally
            {
                this.IsBusy = false;
                this.BusyMessage = String.Empty;
            }
        });

        return task;
    }

Any idea what would cause this? We can say the culprit is our framework, and it may be, but we haven't been able to figure out how this can possibly happen. To get around the issue, we just stopped using ContinueWith() and put all the code in the first section/task.

Edit

Perhaps this is a bug? There is a similar issue here, although it's with Console.ReadKey().

Edit 2 - Call stack immediately after calling GetViewableSites()

Not Flagged > 6324 6 Worker Thread Worker Thread Acme.Mes.Apps.Derating.ViewModels.Controls.GlobalTabControlViewModel.Initialize.AnonymousMethod__9 Normal Acme.Mes.Apps.Derating.ViewModels.dll!Acme.Mes.Apps.Derating.ViewModels.Controls.GlobalTabControlViewModel.Initialize.AnonymousMethod__9() Line 361
Acme.Mes.Client.dll!Acme.Mes.Client.Mvvm.MvvmTools.ViewModelBase.PerformBusyAction.AnonymousMethod__c() Line 494 + 0xe bytes
mscorlib.dll!System.Threading.Tasks.Task.InnerInvoke() + 0x49 bytes
mscorlib.dll!System.Threading.Tasks.Task.Execute() + 0x2e bytes
mscorlib.dll!System.Threading.Tasks.Task.ExecutionContextCallback(object obj) + 0x15 bytes
mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx) + 0xa7 bytes
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx) + 0x16 bytes
mscorlib.dll!System.Threading.Tasks.Task.ExecuteWithThreadLocal(ref System.Threading.Tasks.Task currentTaskSlot) + 0xcb bytes
mscorlib.dll!System.Threading.Tasks.Task.ExecuteEntry(bool bPreventDoubleExecution) + 0xb3 bytes
mscorlib.dll!System.Threading.Tasks.Task.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() + 0x7 bytes
mscorlib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch() + 0x149 bytes
mscorlib.dll!System.Threading._ThreadPoolWaitCallback.PerformWaitCallback() + 0x5 bytes
[Native to Managed Transition]

Community
  • 1
  • 1
Bob Horn
  • 33,387
  • 34
  • 113
  • 219
  • Have you tried with `Task`s that don't use any UI controls? – L.B Oct 01 '13 at 20:32
  • There are no UI controls here. This is all view model and base class stuff. – Bob Horn Oct 01 '13 at 20:41
  • Are you able to produce small but complete sample that demonstrates this problem? – svick Oct 01 '13 at 20:51
  • 1
    What does `IsBusy = false` do? That looks like it would hit the UI... – Jon Skeet Oct 01 '13 at 20:54
  • 1
    We've been through this before as I remember. Same recipe, you really do have to post the stack trace of the thread. Nobody can see with "GetViewableSites" might be doing. – Hans Passant Oct 01 '13 at 20:57
  • @JonSkeet `IsBusy` is a property on the view model, and the view binds to that property. – Bob Horn Oct 01 '13 at 21:00
  • @BobHorn: Right. So the claim that "there are no UI controls here" isn't really correct. In particular, changing those properties on a non-UI thread will end up trying to change the UI elements on a non-UI thread, unless your view model is explicitly marshalling. – Jon Skeet Oct 01 '13 at 21:02
  • @HansPassant I posted the code for `GetViewableSites()`. I don't think it's relevant here. Like I said, I can step all the way through it without issue. – Bob Horn Oct 01 '13 at 21:02
  • @JonSkeet So maybe that's it then... A bound property is changing on a non-UI thread. Not sure why that would cause the issue, but maybe that's it. This application did work in the past when it was only running on .NET 4.0. – Bob Horn Oct 01 '13 at 21:04
  • That *may* be it, but I wouldn't like to say for sure. I'd expect an exception to be thrown - at least when running under the debugger. – Jon Skeet Oct 01 '13 at 21:06
  • That's just more code that doesn't tell me what is really going on. The stack trace of the thread shows why it is blocking, it is on top of the trace. Use the debugger force Luke. – Hans Passant Oct 01 '13 at 21:07
  • No exception. I hit F10 and then nothing. At that point control switches from VS to the WPF UI. – Bob Horn Oct 01 '13 at 21:07
  • WPF marshals scalar property changes to UI thread for you. More a hunch than anything else: please try calling `ConfigureAwait(false)` against the `Task` returned by `PerformBusyAction` before you call `ContinueWith` – Kent Boogaart Oct 01 '13 at 23:16
  • @KentBoogaart It looks like `ConfigureAwait()` is .NET 4.5. My project is .NET 4.0. – Bob Horn Oct 02 '13 at 00:00
  • @HansPassant I posted the call stack in my question. – Bob Horn Oct 02 '13 at 00:08
  • @svick I could probably produce a small, complete sample, but I would need to set up a WPF app using MVVM and I'm weighing that effort against just not using `ContinueWith()`. – Bob Horn Oct 02 '13 at 00:09
  • @KentBoogaart Using `ConfigureAwait()` doesn't make much sense, there is no `await` here. And if you meant something like `task.ConfigureAwait(false).ContinueWith(…)`, that won't even compile. – svick Oct 02 '13 at 00:58
  • @svick I can't reproduce this in a new project. It must be something specific to the project I have, or the framework we're using. – Bob Horn Oct 02 '13 at 01:00
  • @BobHorn Which is exactly why I asked. If we can't reproduce it, I'm afraid we won't be able to help you. And if you can reproduce it, you might find the cause yourself. – svick Oct 02 '13 at 01:04
  • @BobHorn, have you tried `TaskContinuationOptions.ExecuteSynchronously` or `TaskScheduler.FromCurrentSynchronizationContext` with `ContinueWith`, just for the sake of trying? – noseratio Oct 02 '13 at 05:05
  • @Noseratio I just tried both and neither worked. – Bob Horn Oct 02 '13 at 11:31

1 Answers1

0

Try accessing IsBusy and BusyMessage by using the dispatcher.

Dispatcher.BeginInvoke((Action) (() =>
    {
        this.IsBusy = false;
        this.BusyMessage = String.Empty;
    }));