【问题标题】:C# HTTP Request works in Console application but not in WinFormsC# HTTP 请求在控制台应用程序中有效,但在 WinForms 中无效
【发布时间】:2019-06-11 00:09:38
【问题描述】:

最初,我创建了一个控制台应用程序来进行一些测试,从服务器获取 api 调用,并获得了完整的功能和工作。现在,我想进行显示,但是来自控制台应用程序的完全相同的代码在 winforms 应用程序中不起作用。由于一些断点测试,我看到它挂在这一行:

var response = await httpClient.SendAsync(request);

我不明白有什么区别,我已经下载了所有必要的包。我认为这与使用等待有关。谢谢,伊森

这里是排除私有信息的请求方法代码。我意识到最后有一些不必要的组件,但我只是从我最初的测试控制台应用程序中复制和粘贴。使用此方法的所有方法,包括 main,都是异步的。

public async Task<string> APICall(string address)
    {

        string error = "An error was encountered.";
        using (var httpClient = new HttpClient())
        {
            using (var request = new HttpRequestMessage(new HttpMethod("GET"), address))
            {
                request.Headers.TryAddWithoutValidation("Api-Key", "API KEY");
                httpClient.DefaultRequestHeaders.Accept.Add(
                new MediaTypeWithQualityHeaderValue("application/hal+json"));

                var response = await httpClient.SendAsync(request);
                if (response.IsSuccessStatusCode)
                {
                    try
                    {
                        Stream res = await response.Content.ReadAsStreamAsync();
                        var serializer = new JsonSerializer();

                        using (var sr = new StreamReader(res))
                        {
                            using (var jsonTextReader = new JsonTextReader(sr))
                            {
                                return await response.Content.ReadAsStringAsync();


                            }

                        }


                    }
                    catch (IOException e)
                    {
                        Console.WriteLine(e);
                    }
                    catch (NullReferenceException e)
                    {
                        Console.WriteLine(e);
                    }
                }

                request.Dispose();
            }
            httpClient.Dispose();
        }
        return error;
    }

这些是调用问题方法的调用方法,它从 Main 到 startAsync 再到 CallAsync,再到 APICall,即相关方法。

        static async Task Main(string[] args)
    {

                    Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Form1 f = new Form1();
        Caller caller = new Caller();
        string test = await caller.CallAsync();
        foreach (KeyValuePair<string, Server> server in Server.servers)
        {
            f.AddRow(server.Value.name, server.Value.location, server.Value.status, server.Value.ticketStatus);
       }
        Application.Run(f);


    }

public static async Task startAsync()
    {
        Caller caller = new Caller();
        string json = await caller.CallAsync();
        Console.WriteLine(json);
        dynamic name = JsonConvert.DeserializeObject<RootObject>(json);
        List<Location> locs = name._embedded.locations;
        int count = 0;
        foreach (Location loc in locs)
        {
            count++;
            Console.WriteLine(count);
            Server.servers.Add(loc.name, new Server(loc.name, loc.address.city, loc.address.state, loc.id, loc.status, await caller.TicketStatusAsync(loc.id)));

        }
    }
        public async Task<string> CallAsync()
    {
        return await APICall(baseAddress);
    }

【问题讨论】:

  • 您在控制台应用中收到响应了吗?
  • 请提供更多关于代码的上下文。控制台应用程序调用它的方式与 WinForms 应用程序有什么区别? request 是如何构造的,它包含什么?
  • 只是猜测......你有一个deadlock
  • 我猜测某些方法直接或间接(通过其他方法)调用您的方法不是使用 await 而是使用 Wait()Result 。这将阻塞调用任务(通常是 UI 任务),因此 await 无法切换回导致死锁的 UI 任务。这不会在控制台中发生,因为它们没有同步上下文(UI 线程)可以切换回来。
  • 如果没有看到您的方法和调用方法,我不能肯定地说,但是您可以使用SendAsync(…).ConfigureAwait(false) 来避免上下文切换。但实际上,async 本来就是异步的,应该避免调用 Wait/Result。

标签: c# winforms


【解决方案1】:

最有可能的是,在您的调用堆栈中,您正在调用Wait()ResultGetAwaiter().GetResult()。换句话说,您的代码是异步同步的。这在控制台应用程序中工作正常 - 事实上,像这样的一些阻塞是必要的,因此主控制台线程在异步工作完成之前不会退出 - 但它会在其他上下文中死锁,包括 WinForms .

它死锁的原因是await 默认捕获一个上下文,并使用该上下文继续执行async 方法。因此,当您的 await httpClient... 代码运行时,它会捕获当前上下文,然后返回一个不完整的任务。然后,您的调用代码会阻塞在堆栈中,等待该任务完成。

对于 WinForms UI 线程,该上下文是 WinFormsSynchronizationContext,它始终在 UI 线程上执行代码。因此,当SendAsync 完成时,它将继续在UI 线程上执行async 方法。但是,该 UI 线程被阻塞,等待任务完成。在 UI 线程空闲之前,任务无法完成,因此您遇到了死锁。

解决这个死锁的正确方法是remove the sync-over-async antipattern。换句话说,将Wait()ResultGetAwaiter().GetResult() 替换为await

【讨论】:

  • Wait 或 Result 没有在我的任何代码中直接调用。使用此方法的所有方法也是异步的。
  • @Eman936:那么请发一个minimal, verifiable example
  • @Eman936:一个最小的、可验证的示例需要包含所有必要的代码,以便其他人(即我)可以将其复制/粘贴到本地项目中并重现问题。
  • @Eman936:我在您的问题中没有看到任何 WinForms 代码;它对我来说就像一个控制台应用程序。我建议创建一个新的 WinForms 项目,然后添加您的代码(不包括 Main)。
  • 在异步调用之后 OP 不调用 Application.Run() 是否重要?如果我编写一个简短的 Winforms 应用程序并在调用Application.Run() 之前检查Synchronization.Current,结果是null,所以我假设它在消息循环建立之前没有设置。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-06-21
  • 1970-01-01
  • 2017-10-19
  • 2019-11-11
  • 2021-09-12
  • 2019-11-28
  • 1970-01-01
相关资源
最近更新 更多