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*/   }