为什么即使我尝试从工作器更改UI,同步上下文也为null,为什么工作人员在UI线程上等待,即使我不这样做?

我点击一个表单上的按钮,我称之为FooAsync并在完成时阻止UI线程。

以下是代码和我的问题。

 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 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. 为什么同步上下文在我传递给Task.Run中的FooAsync的匿名方法中返回null ,即使我尝试设置button1Text属性?

如果我不从匿名方法中对UI做任何事情,那么同步上下文为null ,这是我对目前对同步上下文的理解所期望的行为。

  1. 为什么会出现这种死锁? 它看起来像传递给Task.Run的匿名方法,即使清楚地在线程池线程上运行,即使没有触摸UI,也就是当我注释掉设置button1Text属性的行时,试图发布一些东西到Windows消息泵。 那是对的吗? 如果是的话,为什么呢?

无论如何,发生了什么? 造成死锁的原因是什么。 我理解UI线程被阻止,但为什么这个其他工作线程试图在UI线程上等待是免费的?

1) Task.Run在启动线程池线程时不设置同步上下文。 一旦你失去了上下文,你就无法“取回它”,除非你在它丢失之前明确地复制了它。 例如, Progress就是使调用在正确的线程上运行的function。 它获取构造函数中当前同步上下文的副本。

2) Task.Run的主体与你的死锁无关,你可以用Task.Delay(10)替换Task.Run(...) Task.Delay(10)并看到同样的问题。 await你在外面做的导致问题。 让我稍微重新编写你的函数来分解正在发生的3个步骤:

 public async Task FooAsync() { Task task = Task.Run(() => { //... }); string result = await task; return result; } 

你编写代码的方式告诉系统运行换行return result; 在同步上下文中,如果它是可用的(它是),但是你使用.Result阻塞线程同步上下文,并且.Result在函数返回值之前不会解除阻塞。 陷入僵局。

解决这个问题的一种方法是告诉return result; 不一定使用同步上下文,即使它可以通过告诉它.ConfigureAwait(false)

 public async Task FooAsync() { Task task = Task.Run(() => { //... }); string result = await task.ConfigureAwait(false); return result; } 

为什么同步上下文在我传递给FooAsync中的Task.Run的匿名方法中返回null,即使我尝试设置button1的Text属性?

在匿名方法中,代码在线程池线程中运行。 在这种情况下,同步上下文为空是正常的。 在正常情况下,当您仅在UI线程中运行时,您应该期望UI应用程序中的同步上下文为非null。

如果您尝试在匿名方法中更改button1.Text的值,则会出现exception,因为只有UI线程才能更新UI。 在这种情况下,.NET不会使用UI线程来更新UI。

为什么会出现这种死锁? 它看起来像传递给Task.Run的匿名方法,即使清楚地在线程池线程上运行,甚至当没有触摸UI时,即当我注释掉设置了button1的Text属性的行时,试图将某些内容发布到Windows消息泵。 那是对的吗? 如果是的话,为什么呢?

因为await Task.Run(() ...正在调度UI线程上的延续,并且由于您使用UI线程同步等待任务(通过.Result ),因此存在死锁。换句话说,由于UI线程忙于等待任务,因此继续无法继续。

如果在FooAsync()删除asyncawaitFooAsync()消除死锁,因为不会尝试在UI线程上继续。

删除死锁的另一种方法是告诉await Task.Run...不要通过调用.ConfigureAwait(false);来捕获同步上下文.ConfigureAwait(false); 在任务上。

无论如何,我认为你可能没有以正确的方式做事。

你可能应该做这样的事情:

 private async void button1_Click(object sender, EventArgs e) { var t = FooAsync(); ... var s = await t; button1.Text = s; } public async Task FooAsync() { var something = await Task.Run(() => DoCPUIntensiveNonUIStuff()); DoSomeUIWork(); return ... } 

在这种情况下,async / await magic将起作用(捕获SynchronizationContext,然后在继续时使用它),并且DoSomeUIWork()方法将使用UI线程运行。

看看这篇关于async / await的文章 。

啊,我犯的经典错误。 我知道了。

当您调用SynchronizationContext.Current ,它会为您提供正在运行的当前线程的同步上下文。

我应该做什么,以及当awaiter通过将await爆炸到状态机来构建延续时,它也是这样做的:

 public async Task FooAsync() { // Will give you the sync ctx of the calling thread // which, in this case, happens to be the UI thread var ctx = SynchronizationContext.Current; return await Task.Run(() => { if (ctx != null) { // Use ctx here // button1.Text = ctx.GetType().Name; Debugger.Break(); } return "Hello"; }); } 

不是这个,这就是我在做的事情:

 public async Task FooAsync() { return await Task.Run(() => { // Will get us the sync context of the worker, // which there is none, thus null var ctx = SynchronizationContext.Current; if (ctx != null) { // Use ctx here // button1.Text = ctx.GetType().Name; Debugger.Break(); } return "Hello"; }); } 

这回答了我的两个问题中的第一个问题。

第二个仍然存在。 为什么要尝试发布到UI线程?

哦,是的,可能是,并且可能是因为它看到当前同步上下文不为空,它尝试获取该上下文并在该上下文中执行工作,而不考虑该方法的主体正在做什么。 是对的吗?