C# 在任务继续中阻止异步HttpClient调用时发生死锁

C# 在任务继续中阻止异步HttpClient调用时发生死锁,c#,async-await,task,deadlock,dotnet-httpclient,C#,Async Await,Task,Deadlock,Dotnet Httpclient,我正试图集中精力同步调用异步函数和死锁 在下面的示例中,我阻止了一个异步调用,该调用在内部使用ConfigureAwait(false),据我所知,它应该可以防止死锁。对web服务的第一次调用不会死锁。但是,第二个问题发生在同步回UI线程死锁的延续中 GetBlocking_Click是WPF应用程序中的单击事件,因此第一个和第二个请求都发生在UI线程上 private void GetBlocking_Click(object sender, RoutedEventArgs e)

我正试图集中精力同步调用异步函数和死锁

在下面的示例中,我阻止了一个异步调用,该调用在内部使用ConfigureAwait(false),据我所知,它应该可以防止死锁。对web服务的第一次调用不会死锁。但是,第二个问题发生在同步回UI线程死锁的延续中

GetBlocking_Click是WPF应用程序中的单击事件,因此第一个和第二个请求都发生在UI线程上

      private void GetBlocking_Click(object sender, RoutedEventArgs e)
    {
        const string uri = "http://www.mywebservice.com";
        var example1 = GetAsync(uri).Result;

        Task.FromResult(true)
            .ContinueWith(t =>
            {
                var example2 = GetAsync(uri).Result;
            }, 
            TaskScheduler.FromCurrentSynchronizationContext()).Wait();
    }

    private async Task<string> GetAsync(string url)
    {
        using (var client = new HttpClient())
        {
            var responseMessage = await client.GetAsync(url).ConfigureAwait(false);

            return await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
        }
    }
private void GetBlocking\u单击(对象发送方,路由目标)
{
常量字符串uri=”http://www.mywebservice.com";
var example1=GetAsync(uri).Result;
Task.FromResult(true)
.ContinueWith(t=>
{
var example2=GetAsync(uri).Result;
}, 
TaskScheduler.FromCurrentSynchronizationContext()).Wait();
}
专用异步任务GetAsync(字符串url)
{
使用(var client=new HttpClient())
{
var responseMessage=await client.GetAsync(url).ConfigureAwait(false);
返回wait-responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
}
}

你能解释一下这两个电话的区别吗

您在UI线程中。从该线程调用
Task.FromResult
并创建一个任务。然后向该任务添加需要在UI线程中运行的延续。该延续被添加到队列中,供消息泵处理

然后等待该延续在UI线程中完成。该延续正在等待UI可用,以便甚至启动延续的主体,这将最终调用
GetAsync
。这是一个僵局

延续的主体是什么并不重要;以下代码因相同原因而死锁:

private void GetBlocking_Click(object sender, RoutedEventArgs e)
{
    Task.FromResult(true)
        .ContinueWith(t =>{ }, 
            TaskScheduler.FromCurrentSynchronizationContext())
        .Wait();
}

至于修复,您首先不应该同步地等待异步操作。要么使整个过程异步,要么使其全部同步。

不要同步阻止异步方法。时期事实上,这样做总是错误的<代码>等待改为单击处理程序中的方法。实际上,此继续操作会运行。是继续中的GetAsync造成了死锁。如果我通过
task.Run
实际启动任务,例如
task.Run(()=>true)
,则您描述的行为是有效的,在这种情况下,另一个线程池线程返回true。但是,在使用
Task.FromResult
的示例中,继续运行,但GetAsync死锁。我完全同意async,但我只是想理解为什么第一个示例有效而第二个无效。@AlexK不,那不是真的。您将当前上下文通过调用传递到
ContinueWith,因此尽管将立即安排继续,但在同步上下文实际执行该方法之前,它实际上不会开始执行。它将无法执行该方法,直到它正在泵送的当前消息(单击事件处理程序)完成。正如我所说,
GetAsync
根本不涉及死锁。就我在WPF应用程序上调试它所见,这是真的。使用
Task.FromResult(true)
的第一个代码段在
var example2=GetAsync(uri).Result上中断,然后死锁。如果我用
Task.Run(()=>true)
替换
Task.FromResult(true)
,则应用程序会死锁,而不会到达continuation。你能解释一下这两段代码之间的区别吗?无论如何,我接受你的回答,因为你给我指明了正确的方向。谢谢