C# HTTP 请求适用于控制台应用程序,但不适用于 WinForms

问题描述 投票:0回答:2

最初,我创建了一个控制台应用程序来进行一些测试,从服务器获取 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);
    }
c# winforms
2个回答
5
投票

最有可能的是,在调用堆栈的更上方,您正在调用

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

它死锁的原因是因为

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

对于 WinForms UI 线程,该上下文是

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

解决此死锁的正确方法是删除异步同步反模式。换句话说,将

Wait()
Result
GetAwaiter().GetResult()
替换为
await


0
投票

我浪费了 5 个多小时试图解决这个难题,直到我在这里找到了 Stephen 的回复,甚至 Copilot 也无法知道这一点。谢谢斯蒂芬。

这是我所做的更改,它适用于我的 WinForm 项目:

    //This’s the code that works in console app but in Win Form doesn’t
    static void Main()
    {
        int statusCode = GetResponse_inConsole(10000).Result;
        Console.WriteLine($"Response status code: {statusCode}");
    }

    static async Task<int> GetResponse_inConsole(int timeout)
    {
        HttpClient client = new()
        {
            Timeout = TimeSpan.FromMilliseconds(timeout)
        };

        try
        {
            HttpResponseMessage response = await client.GetAsync("http://google.com");
            return (int)response.StatusCode;
        }
        catch {return 0;}
    }

使用 Windows 窗体,创建一个可供任务访问的公共字符串变量和一个秒表来测量超时

    static string sharedString;         
    private void button1_Click(object sender, EventArgs e)
    {
        var timeout = 5000;
        Stopwatch sw = Stopwatch.StartNew();
        GetResponse_inForm(timeout);

        while(sw.ElapsedMilliseconds < timeout)
        {
            Application.DoEvents();
        }
        sw.Stop();

        MessageBox.Show(sharedString);
    }

    static async Task GetResponse_inForm(int timeout)
    {
        HttpClient client = new()
        {
            Timeout = TimeSpan.FromMilliseconds(timeout)
        };

        try
        {
            HttpResponseMessage response = await client.GetAsync("http://google.com");
            sharedString = response.StatusCode.ToString();
        }
        catch (Exception ex)
        {
            sharedString = $"Error: {ex.Message}";
        }
    }
© www.soinside.com 2019 - 2024. All rights reserved.