Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is the synchronization context null even when I try to change UI from a worker and why does the worker wait on the UI thread even when I don't?

I have a button on a form in the click of which I call FooAsync and block the UI thread on its completion.

Below is the code and my questions.

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace SynContextIfIDontTouchUIInWorkerThread
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

#pragma warning disable 1998
        private async void button1_Click(object sender, EventArgs e)
        {
            // Nicely prints out the WindowsForms.SynchronizationContext
            // because we *are* indeed on the UI thread
            this.Text = SynchronizationContext.Current.GetType().Name;
            Thread.CurrentThread.Name = "UI Thread";
            Debug.Print(Thread.CurrentThread.Name);

            var t = FooAsync();

            // CompletedSynchronously is false, 
            // so the other work was indeed run on a worker thread
            button1.Text = (t as IAsyncResult).CompletedSynchronously 
                            ? "Sync" : "Async";

            // block the UI thread
            // Code freezes here
            var s = t.Result;

            button1.Text = s;
        }
#pragma warning restore 1998

        public async Task<string> FooAsync()
        {
            return await Task.Run(() => 
            {
                // Whether or not I touch the UI in this worker
                // thread, the current sync context returns null.
                // Why is that?
                // However, it looks like this thread is posting
                // something to the UI thread and since the UI
                // thread is also waiting for this guy to complete
                // it results in a dead lock. Why is that when
                // I am not even touching the UI here. Why
                // is this guy assuming that I have to post
                // something to message queue to run on the UI thread?
                // Could it be that this guy is actually running on
                // the UI thread?
                var ctx = SynchronizationContext.Current;

                Debugger.Break();

                // Current thread name evaluates to null
                // This clearly means it is a thread pool thread
                // Then why is the synchronization context null
                // when I uncomment out the line that changes the text
                // of button1?
                Debug.Print(Thread.CurrentThread.Name);

                if (ctx != null)
                {
                    // Post to Windows message queue using the UI thread's sync ctx
                    // button1.Text = ctx.GetType().Name;
                    Debugger.Break();
                }

                return "Hello";
            });
        }
    }
}
  1. Why is the synchronization context returning null in the anonymous method that I pass to Task.Run in FooAsync even when I try to set button1's Text property?

And the synchronization context is null if I don't do anything to the UI from within the anonymous method, which is the behavior I expected from my present understanding of a synchronization context.

  1. Why is there this dead-lock? It looks like the anonymous method passed to Task.Run, even though clearly running on a thread pool thread and even when not touching the UI, i.e. when I comment out the line that sets button1's Text property, is trying to post something to the Windows message pump. Is that correct? And if it is, why so?

At any rate, what is happening? What is causing the dead lock. I understand that the UI thread is blocked, but why is this other worker thread trying to wait on the UI thread to be free?

like image 209
Water Cooler v2 Avatar asked Jun 04 '16 21:06

Water Cooler v2


People also ask

What is a synchronization context?

SynchronizationContext is a representation of the current environment that our code is running in. That is, in an asynchronous program, when we delegate a unit of work to another thread, we capture the current environment and store it in an instance of SynchronizationContext and place it on Task object.

Does a Windows service have a synchronization context?

Bookmark this question. Show activity on this post. As far as I know, there isn't a synchronization context in a Windows Service application.


1 Answers

Why is the synchronization context returning null in the anonymous method that I pass to Task.Run in FooAsync even when I try to set button1's Text property?

Inside the anonymous method, the code runs in a thread-pool thread. It is normal in this case for the synchronization context to be null. In the normal cases, you should expect the synchronization context in UI applications to be non-null when you are running within the UI thread only.

If you try to change the value of button1.Text inside the anonymous method, you will get an exception because only the UI thread can update the UI. .NET does not do any magic in this case to use the UI thread to update the UI.

Why is there this dead-lock? It looks like the anonymous method passed to Task.Run, even though clearly running on a thread pool thread and even when not touching the UI, i.e. when I comment out the line that set's button1's Text property, trying to post something to the Windows message pump. Is that correct? And if it is, why so?

Because await Task.Run(() ... is scheduling a continuation on the UI thread, and since you are using the UI thread to synchronously wait for the task (via .Result), then there is a deadlock. In other words, the continuation cannot proceed because the UI thread is busy waiting for the task.

If you remove the async and await in FooAsync(), you will get rid of the deadlock because no continuations on the UI thread will be attempted.

Another way to remove the dead lock is to tell the await for the Task.Run... not to capture the synchronization context by calling .ConfigureAwait(false); on the task.

In any case, I think that you are probably not doing things in the correct way.

You probably should be doing something like this:

private async void button1_Click(object sender, EventArgs e)
{
    var t = FooAsync();

    ...

    var s = await t;

    button1.Text = s;
}

public async Task<string> FooAsync()
{
    var something = await Task.Run(() => DoCPUIntensiveNonUIStuff());

   DoSomeUIWork();

   return ...
}

In this case, the async/await magic will work (capturing the SynchronizationContext and then using it when continuing), and the DoSomeUIWork() method will run using the UI thread.

Take a look at this article about async/await.

like image 94
Yacoub Massad Avatar answered Nov 01 '22 18:11

Yacoub Massad