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";
});
}
}
}
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.
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?
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.
Bookmark this question. Show activity on this post. As far as I know, there isn't a synchronization context in a Windows Service application.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With