Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/301.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/fortran/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何触发(而不是避免!)HttpClient死锁_C#_Async Await_Deadlock_Dotnet Httpclient_Qa - Fatal编程技术网

C# 如何触发(而不是避免!)HttpClient死锁

C# 如何触发(而不是避免!)HttpClient死锁,c#,async-await,deadlock,dotnet-httpclient,qa,C#,Async Await,Deadlock,Dotnet Httpclient,Qa,关于如何避免从同步代码调用异步代码(例如,HttpClient方法)中的死锁,有很多问题,如。我知道避免这些僵局的各种方法 相比之下,我想了解在测试期间加重或触发错误代码死锁的策略 下面是一个最近给我们带来问题的错误代码示例: public static string DeadlockingGet(Uri uri) { using (var http = new HttpClient()) { var response = http.GetAsync(uri).

关于如何避免从同步代码调用异步代码(例如,
HttpClient
方法)中的死锁,有很多问题,如。我知道避免这些僵局的各种方法

相比之下,我想了解在测试期间加重或触发错误代码死锁的策略

下面是一个最近给我们带来问题的错误代码示例:

public static string DeadlockingGet(Uri uri)
{
    using (var http = new HttpClient())
    {
        var response = http.GetAsync(uri).Result;
        response.EnsureSuccessStatusCode();
        return response.Content.ReadAsStringAsync().Result;
    }
}
它是从ASP.NET应用程序调用的,因此具有非
null
SynchronizationContext.Current
,这为潜在的死锁火灾提供了燃料

除此之外,这段代码在我们公司的一台服务器上死锁了。。。但只是偶尔

我试图重新设置死锁 我在QA工作,所以我试图通过一个单元测试来重新设置死锁,该测试会命中Fiddler侦听器端口的本地实例:

public class DeadlockTest
{
    [Test]
    [TestCase("http://localhost:8888")]
    public void GetTests(string uri)
    {
        SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
        var context = SynchronizationContext.Current;
        var thread = Thread.CurrentThread.ManagedThreadId;
        var result = DeadlockingGet(new Uri(uri));
        var thread2 = Thread.CurrentThread.ManagedThreadId;
    }
}
有几件事需要注意:

  • 默认情况下,单元测试的SynchronizationContext.Current为空。因此,我使用
    SetSynchronizationContext
    将其设置为特定上下文,以便更接近地模拟ASP.NET或UI上下文中发生的情况

  • 我已将Fiddler配置为等待一段时间(约1分钟),然后再响应。我从同事那里听说,这可能有助于重新启动僵局(但我没有确凿的证据证明情况如此)

  • 我已经使用调试器运行了它,以确保
    context
    是非
    null
    thread==thread2

不幸的是,我没有幸在这个单元测试中触发死锁。无论Fiddler中的延迟有多长,它都会结束,除非延迟超过了HttpClient的100秒默认
超时时间(在这种情况下,它只是在异常情况下爆炸)


我是不是缺少了一种可以点燃僵局之火的成分?我想重新设置死锁,只是为了确定我们最终的解决方案是否有效。

您无法重现此问题,因为
SynchronizationContext
本身并没有模仿ASP.NET安装的上下文。基本
SynchronizationContext
不执行锁定或同步,但ASP.NET上下文执行锁定或同步:因为
HttpContext.Current
不是线程安全的,也不是存储在
LogicalCallContext
中,要在线程之间传递,
AspNetSynchronizationContext
对线程执行一些工作。恢复任务时还原
HttpContext.Current
,然后单击b。锁定以确保给定上下文中仅运行一个任务

MVC也存在类似的问题:


这里给出的方法是使用上下文测试代码,确保从不在上下文中调用
Send
Post
。如果是,则表示死锁行为。要解决此问题,请将方法树一直设置为async
,或者在某个位置使用ConfigureAwait(false),这实际上将任务完成与同步上下文分离。有关更多信息,本文详细介绍了

您似乎认为设置任何同步上下文都可能会导致异步代码死锁,但事实并非如此。在asp.net和UI应用程序中阻止异步代码是危险的,因为它们具有特殊的单线程主线程。在UI应用程序中,也就是主UI线程中,ASP.NET应用程序中有许多这样的线程,但对于给定的请求,只有一个请求线程

ASP.NET和UI应用程序的同步上下文是特殊的,因为它们基本上是向一个特殊线程发送回调。因此,当:

  • 在这个线程上执行一些代码
  • 从该代码执行一些异步
    任务
    ,并阻塞其
    结果
  • 任务
    具有等待语句
  • 死锁将会发生。为什么会发生这种情况?因为异步方法的延续是
    Post
    ed到当前同步上下文的。我们上面讨论的那些特殊上下文将把这些延续发送到特殊的主线程。您已经在同一个线程上执行了代码,并且它已经被阻塞了,因此出现了死锁

    那你做错了什么?首先,
    SynchronizationContext
    不是我们上面讨论的特殊上下文-它只是将continuations发布到线程池线程。你需要另一个来做测试。您可以使用现有的(如
    WindowsFormsSynchronizationContext
    ),也可以创建行为相同的简单上下文(示例代码,仅用于演示):

    class QueueSynchronizationContext:SynchronizationContext{
    private readonly BlockingCollection _queue=new BlockingCollection(new ConcurrentQueue());
    公共队列同步上下文(){
    新线程(()=>
    {
    foreach(变量项在_queue.GetConsumingEnumerable()中){
    项目1(项目2);
    }
    }).Start();
    }        
    公共重写void Post(sendorpostd,对象状态){
    _Add(新元组(d,state));
    }
    公共覆盖无效发送(SendorPostD,对象状态){
    //Send应该是同步的,所以我们应该在这里阻塞,但我们不会打扰
    //因为这个问题不重要
    _Add(新元组(d,state));
    }
    }
    
    它所做的只是将所有回调放在单个队列中,并在单独的单个线程上逐个执行它们

    使用此上下文模拟死锁很容易:

    class Program {
        static void Main(string[] args)
        {
            var ctx = new QueueSynchronizationContext();
            ctx.Send((state) =>
            {
                // first, execute code on this context
                // so imagine you are in ASP.NET request thread,
                // or in WPF UI thread now                
                SynchronizationContext.SetSynchronizationContext(ctx);
                Deadlock(new Uri("http://google.com"));   
                Console.WriteLine("No deadlock if got here");
            }, null);
            Console.ReadKey();
        }
    
        public static void NoDeadlock(Uri uri) {
            DeadlockingGet(uri).ContinueWith(t =>
            {
                Console.WriteLine(t.Result);
            });
        }
    
        public static string Deadlock(Uri uri)
        {
            // we are on "main" thread, doing blocking operation
            return DeadlockingGet(uri).Result;
        }
    
        public static async Task<string> DeadlockingGet(Uri uri) {
            using (var http = new HttpClient()) {
                // await in async method
                var response = await http.GetAsync(uri);
                // this is continuation of async method
                // it will be posted to our context (you can see in debugger), and will deadlock
                response.EnsureSuccessStatusCode();
                return response.Content.ReadAsStringAsync().Result;
            }
        }
    }
    
    类程序{
    静态void Main(字符串[]参数)
    {
    var ctx=新的QueueSynchronizationContext();
    ctx.Send((状态)=>
    {
    //首先,在此上下文上执行代码
    //假设您在ASP.NET请求线程中,
    //或者现在在WPF UI线程中
    SynchronizationContext.SetSynchronizationContext(ctx);
    死锁(新Uri(“http://google.com"));
    
    class Program {
        static void Main(string[] args)
        {
            var ctx = new QueueSynchronizationContext();
            ctx.Send((state) =>
            {
                // first, execute code on this context
                // so imagine you are in ASP.NET request thread,
                // or in WPF UI thread now                
                SynchronizationContext.SetSynchronizationContext(ctx);
                Deadlock(new Uri("http://google.com"));   
                Console.WriteLine("No deadlock if got here");
            }, null);
            Console.ReadKey();
        }
    
        public static void NoDeadlock(Uri uri) {
            DeadlockingGet(uri).ContinueWith(t =>
            {
                Console.WriteLine(t.Result);
            });
        }
    
        public static string Deadlock(Uri uri)
        {
            // we are on "main" thread, doing blocking operation
            return DeadlockingGet(uri).Result;
        }
    
        public static async Task<string> DeadlockingGet(Uri uri) {
            using (var http = new HttpClient()) {
                // await in async method
                var response = await http.GetAsync(uri);
                // this is continuation of async method
                // it will be posted to our context (you can see in debugger), and will deadlock
                response.EnsureSuccessStatusCode();
                return response.Content.ReadAsStringAsync().Result;
            }
        }
    }