C# 等待和僵局预防-澄清?
我读过关于C# 等待和僵局预防-澄清?,c#,.net,task-parallel-library,async-await,C#,.net,Task Parallel Library,Async Await,我读过关于Task.ConfigureAwait的文章,它可以帮助防止异步代码中的死锁 查看此代码:(我知道我不应该执行.Result,但这是问题的一部分) 问题: (我试图理解这段代码如何帮助解决这个问题——通过上下文POV) 行#3和行#10是否捕获不同的上下文 我认为流动的方式正确吗: 第#3行调用#6(调用#10)并看到它还没有完成,所以它等待(为#3=UI线程捕获上下文) 稍后,第#10行捕获另一个上下文(我将称之为newContext),在它完成后,返回到“newContext”,
Task.ConfigureAwait
的文章,它可以帮助防止异步代码中的死锁
查看此代码:(我知道我不应该执行.Result
,但这是问题的一部分)
问题:
(我试图理解这段代码如何帮助解决这个问题——通过上下文POV)
#3
和行#10
是否捕获不同的上下文- 第#3行调用#6(调用#10)并看到它还没有完成,所以它等待(为#3=UI线程捕获上下文)
- 稍后,第#10行捕获另一个上下文(我将称之为newContext),在它完成后,返回到“newContext”,然后释放UI上下文(线程)
我假设这是
WinForms/WPF/Asp.NET
应用程序,因此在第3行,UI
线程同步上下文将被捕获,任务将在线程池线程中运行。因此,将从线程池线程调用第10行,并使用默认调度程序。没有不同的上下文。在这两种情况下,SyncrhonizationContext
都是单线程UI同步上下文(只要您没有使用ConfigureAwait(false)
)
当UI线程同步等待自身时,就会发生死锁。您可以通过使用ConfigureAwait(false)
避免UI线程,或者通过避免使用Task.Result
不首先阻止它来解决这个问题
“一直执行async
操作”解决死锁的原因是UI线程不再被阻塞,可以自由运行两个async
操作的继续
因此:
SyncrhonizationContext
GetAsync
任务完成后,行#11
将在UI线程(未被阻止)上恢复,这将依次完成GetPageStatus
任务。然后,行#4
也将在UI线程上恢复SynchronizationContext
只确保某些工作由专用线程(串行)完成。只要线程可以自由执行该工作单元,死锁就永远不会发生
第3行和第10行是否捕获了不同的上下文
从代码的外观来看,不是。它们都将捕获相同的UI同步上下文,因为您不使用ConfigureAwait(false)
,这将阻止将延续封送回UI上下文
我认为流动的方式正确吗:
第3行调用第6行(调用第10行),发现它还没有结束,
所以它在等待(为#3=UI线程捕获上下文)
稍后,第10行捕获另一个上下文(我称之为
newContext)完成后,返回到“newContext”,然后
释放UI上下文(线程)
差不多。您的呼叫中没有创建“新上下文”。它始终是相同的UI同步上下文。例如,如果有两个异步调用一个接一个,当其中一个使用ConfigureAwait(false)
时,第二个调用将继续在线程池线程上执行
至于可视化,它确实正确地捕获了代码的执行流。如果是,什么时候创建新的上下文?你能举个例子吗?。可视化流程如何,对吗?@RoyiNamir上下文通常不会创建。有一个用于UI线程(WinForms、WPF)和一个用于asp.net。所有其他代码都不使用同步上下文。@l3arnon如果需要,您可以创建自定义同步上下文:)@RoyiNamir以下是一个示例,用于主动设置不同的SC:@I3arnon Yes I Haven environment with async:-)p.s。一旦您向我发送指向显示许多冗余等待代码行的站点的链接。你有链接吗?我不认为“捕获上下文”是这里的问题,它是同步的
.Result()。查找罪犯对Result
或Wait()
的调用。在事件处理程序上使用async void
,并等待异步method@PanagiotisKanavos我只是。这是一个特殊情况。链接不正确。正确的代码块是在事件处理程序中执行异步代码的正确方法<当您不知道如何调用代码时,在库代码中使用code>ConfigureAwait
。在本例中,您确实希望在UI线程上运行continuations,因为您将不再需要其他方式的所有Invoke
调用ConfigureWait(false)的要点是没有上下文。只需使用调试器进行尝试,查看SynchronizationContext.Current。它将是null
。这将强制继续在线程池线程而不是UI线程上运行。底层调用是SynchronizationContext.Post(),而不是DispatcherSynchronizationContext.Post()。因此没有死锁发生。我的意思是-我没有创建新线程:-)(关于:因此将从线程池线程调用第10行,并使用默认调度程序。)@HamletHakobyan这是错误的。此调用根本不会生成任何新线程。在将工作封送回UI线程之前,它只会使用IOCP一小段时间。@YuvalItzchakov,你说得对,GetPageStatus()
不会启动任何任务。尽管GetAsync()
does.GetAsync返回一个任务。但它不会创建新线程。在内部(使用反射器),它使用TCS@YuvalItzchakov-Yuval,我没有弄错,是吗?@RoyiNamir这是因为实际的异步
private void Button_Click(object sender, RoutedEventArgs e)
{
string result = GetPageStatus().Result;
Textbox.Text = result;
}
public async Task<string> GetPageStatus()
{
using (var httpClient = new HttpClient())
{
var response = await httpClient.GetAsync("http://www.google.com");
return response.StatusCode.ToString();
}
}
/*1*/ private async void Button_Click(object sender, RoutedEventArgs e)
/*2*/ {
/*3*/ string result = await GetPageStatus();
/*4*/ Textbox.Text = result;
/*5*/ }
/*6*/ public async Task<string> GetPageStatus()
/*7*/ {
/*8*/ using (var httpClient = new HttpClient())
/*9*/ {
/*10*/ var response = await httpClient.GetAsync("http://www.google.com");
/*11*/ return response.StatusCode.ToString();
/*12*/ }
/*13*/ }