Question

I have a task which, when completed, is supposed to continue with another task that shows a winform (the winform was previously initialised on the UI thread, so it does have a handle).

    private static Task RunningTask
    {
        get;
        set;
    }
    public static UpdaterTool.Forms.UpdateResult UpdateResultForm;

    private void DoWork()
    {
        UpdateResultForm = new Forms.UpdateResult(); 
        //the next line forces the creation of the handle - 
        //otherwise InvokeRequired will later on return false.
        var hackHandle = UpdateResultForm.Handle; 

        var ctx = TaskScheduler.FromCurrentSynchronizationContext();

        RunningTask = Task.Factory.StartNew(() => DownloadAndInstallFiles(), CancelTokenSource.Token)
            .ContinueWith(_ => WorkComplete(), CancelTokenSource.Token, TaskContinuationOptions.NotOnFaulted, ctx);
    }

    private void WorkComplete()
    {
       ShowResultForm();
    }

    private void ShowResultForm()
    {
        if (UpdateResultForm.InvokeRequired)
        {
            try
            {
                UpdateResultForm.Invoke(new MethodInvoker(ShowResultForm));
            }
            catch { }
            return;
         }
         UpdateResultForm.Show();

     }

The problem is that no matter what combination of overloads for the ContinueWith() I use, the UpdateResultForm is either not shown at all(meaning the continuation does not happen, the worker hangs at "running"), or when it is, it hangs the UI, like its expecting the worker thread to finish or something. I dont understand why this happens when I tried to show it on the UI thread, by using FromCurrentSynchronizationContext().

In my understanding, inside the DoWork method I start on the UI thread (which is why I initialise the form there). When the code enters Task.Factory.StartNew, it switches to the working thread. When it completes, it continues with WorkComplete which just shows the previously initialised form, on the UI thread.

What am I missing? Thanks,

Was it helpful?

Solution

Using InvokeRequired is a strong anti-pattern. It's not that common to have no idea on what thread a method runs. That's the case here as well, you already used TaskScheduler.FromCurrentSynchronizationContext() so you know that the task runs on the UI thread. There's no point in checking again. Which also eliminates the need to create the form handle early.

The kind of problems you are having can be caused by running DoWork() from a worker thread. Maybe you called it from a task as well. Which makes FromCurrentSynchronizationContext() return the wrong context. And will display the form on a thread that doesn't pump a message loop, it will be dead as a doornail. Or by blocking the UI thread, waiting for the task to finish. That will always cause deadlock, the task cannot complete unless the UI thread goes idle and can execute the ShowResultForm() method.

OTHER TIPS

I think the solution to this is: Is DoWork() called on any Thread other than the UI Thread?

If yes, then consider instantiating the UpdateResultForm somwhere on the UI Thread or if this is not possible then somwhere within your ContinueWith operation.

If no then there shouldn't be a problem. The UpdateResultForm.Handle could have caused some trouble. However, this handle is no longer needed in this case, as you are on the UI Thread already and therefore don't have to check wether Invoke is required or not.

In any of both cases you could try rewriting your DoWork method like this:

     private void DoWork()
            {
                var ctx = TaskScheduler.FromCurrentSynchronizationContext();

                RunningTask = Task.Factory
                    .StartNew(DownloadAndInstallFiles, CancelTokenSource.Token)
                    .ContinueWith(_ => ShowResultForm(), CancelTokenSource.Token, TaskContinuationOptions.NotOnFaulted, ctx);
            }

            private void ShowResultForm()
            {
                UpdateResultForm = new Forms.UpdateResult();
                // Some other code
                UpdateResultForm.Show();
            }

Even WorkComplete is no longer required as it merely passes the call thru. Hope this will help you.

If you're using SynchronizationContext then you don't need the InvokeRequired/Invoke. The only way you can run into the problem you're seeing here is if DoWork is invoked on a thread other than the UI thread.

e.g. if I take the code you provided, create my own UpdateResult form, add a CancelTokenSource member and initialize it in the constructor, and run DoWork from the UI thread, it works fine.

I solved it. The answers on this thread are all correct, the issue here was, just like Hans suggested, that FromCurrentSynchronizationContext() was returning a wrong context instead of the expected UI one. This happened because DoWork was called from a worker thread, and not from the UI one.

The solution here in my particular case, was to call DoWork from a UI context, which means I had to make a WindowsFormsSynchronizationContext object just like this blog post explains.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top