Wat*_* v2 7 .net c# asynchronous task-parallel-library async-await
我点击一个表单上有一个按钮,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<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";
});
}
}
}
Run Code Online (Sandbox Code Playgroud)
null在我传递给匿名方法Task.Run中FooAsync,甚至当我尝试设置button1的Text属性?同步上下文是指null我不从匿名方法中对UI做任何事情,这是我对目前对同步上下文的理解所期望的行为.
Task.Run,即使在一个线程池线程显然运行,即使不接触UI,即当我注释掉设置行button1的Text性质,试图才能发布到Windows消息泵.那是对的吗?如果是,为什么呢?无论如何,发生了什么?造成死锁的原因是什么.我知道UI线程被阻止,但为什么这个其他工作线程试图在UI线程上等待是免费的?
为什么同步上下文在我传递给FooAsync中的Task.Run的匿名方法中返回null,即使我尝试设置button1的Text属性?
在匿名方法内部,代码在线程池线程中运行.在这种情况下,同步上下文为空是正常的.在正常情况下,当您仅在UI线程中运行时,您应该期望UI应用程序中的同步上下文为非null.
如果您尝试更改button1.Text匿名方法内部的值,则会出现异常,因为只有UI线程才能更新UI.在这种情况下,.NET不会使用UI线程来更新UI.
为什么会出现这种死锁?它看起来像传递到Task.Run匿名方法,即使在一个线程池线程显然运行,即使不接触UI,即当我注释掉设置的按钮1的Text属性行,试图才能发布到Windows消息泵.那是对的吗?如果是,为什么呢?
因为await Task.Run(() ...正在调度UI线程上的延续,并且由于您正在使用UI线程同步等待任务(via .Result),因此存在死锁.换句话说,由于UI线程忙于等待任务,因此无法继续.
如果删除async和awaitin FooAsync(),则会消除死锁,因为不会尝试在UI线程上继续.
以去除死锁的另一种方式是告诉await了Task.Run...没有通过调用来捕捉同步上下文.ConfigureAwait(false);的任务.
无论如何,我认为你可能没有以正确的方式做事.
你可能应该做这样的事情:
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 ...
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,async/await magic将起作用(捕获SynchronizationContext,然后在继续时使用它),并且该DoSomeUIWork()方法将使用UI线程运行.
1)Task.Run启动线程池线程时不设置同步上下文。一旦丢失上下文,您就无法“找回它”,除非您在丢失之前明确地复制了它。例如,这就是Progress使调用在正确的线程上运行的方法。它在构造函数中获取当前同步上下文的副本。
2) the 的正文Task.Run与你的死锁无关,你可以用Task.Run(...)a替换 theTask.Delay(10)并看到同样的问题。正是await您在其之外所做的事情导致了问题。让我稍微重写一下你的函数,以分解正在发生的 3 个步骤:
public async Task<string> FooAsync()
{
Task<String> task = Task.Run(() =>
{
//...
});
string result = await task;
return result;
}
Run Code Online (Sandbox Code Playgroud)
您编写代码的方式告诉系统在return result;同步上下文可用(确实如此)的情况下在同步上下文上运行该行,但是您正在使用 阻塞线程同步上下文,并且.Result在.Result函数返回值之前不会解除阻塞。陷入僵局。
解决此问题的一种方法是告诉return result;不必使用同步上下文,即使它可用.ConfigureAwait(false)
public async Task<string> FooAsync()
{
Task<String> task = Task.Run(() =>
{
//...
});
string result = await task.ConfigureAwait(false);
return result;
}
Run Code Online (Sandbox Code Playgroud)