Winform中使用HttpClient(设置最大超时响应时间)调用接口并做业务处理时界面卡住,使用async Task await异步任务编程优化

发布时间 2023-03-24 18:04:04作者: 霸道流氓

场景

Winform中怎样使用HttpClient调用http的get和post接口并将接口返回json数据解析为实体类:

https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/124157296

参考前面使用HttpClient调用http的get和post接口的小示例,

需要定位调用http的get接口并对接口返回数据进行后续处理。

关于定时器的使用在下面文章中有涉及到

Winform中使用mysqldump实现选择部分表定期备份mysql数据库:

https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/120650090

注:

博客:
https://blog.csdn.net/badao_liumang_qizhi

实现

1、初次业务实现逻辑比较简单,未考虑任务同步执行堵塞UI线程的情况。

在测试时发现,当http接口不通时,而又未设置httpClient的最大超时响应时间,

会导致页面卡死无响应。

优化前的代码:

        private void convertPositionControl() {
            try
            {
                //获取接口数据
                var positionData = requestGetHttpData(positionCalculateUrl);
                //http请求不到数据,啥也不干
                if (null == positionData)
                {
                    return;
                }//请求到数据,则进行数据处理
                else
                {
 
                }
            }
            catch (Exception exception)
            {
                textBox_log.AppendText(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + ":获取接口出错:");
                textBox_log.AppendText("\r\n");
                textBox_log.AppendText(exception.Message);
            }
        }

 

上面是在定时器中具体执行的代码,省略部分逻辑,其中请求http接口的方法是

requestGetHttpData,参数getAllBaseStationInfoUrl是接口url。

然后请求接口的方法具体实现是

        private string requestGetHttpData(string apiUrl)
        {
            try {
                //调用接口请求数据
                var originAddressUrl = apiUrl;
                //请求接口数据
                if (null == httpClient) {
                    httpClient = new HttpClient();
                }
                var url = new Uri(originAddressUrl);
                var response = httpClient.GetAsync(url).Result;
                var data = response.Content.ReadAsStringAsync().Result;
                return data;
            }
            catch (Exception exception) {   
                textBox_log.AppendText(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + ":调用接口" + apiUrl + "异常:" + exception.Message);
                textBox_log.AppendText("\r\n");
                return null;
            }
        }

这里直接未作任何考虑,只是考虑接口正常的情况。

但是当接口不存在或者报错时就会导致页面卡死。

注意:

HttpClient的预热机制,不要在每次请求接口时都要初始化

httpClient = new HttpClient();

这里放在页面加载完成之后进行初始化一次

        private void Form1_Load(object sender, EventArgs e)
        {
            httpClient = new HttpClient();
            httpClient.Timeout = TimeSpan.FromSeconds(httpClientMaxResponseSeconds);
        }

另外需要给httpClient设置最大响应超时时长

2、C# 中 HttpClient设置最大响应超时时长

 httpClient.Timeout = TimeSpan.FromSeconds(httpClientMaxResponseSeconds);

这里httpClientMaxResponseSeconds是

private double httpClientMaxResponseSeconds = 2;

这里设置为2秒。

后续建议将其优化为单例模式或其他更好的模式。

3、上面卡住问题是因为在同步执行的方法中,请求接口的方法会堵塞UI线程/主线程。

需要将上面请求接口的方法修改成异步任务执行的机制,避免影响UI线程。

这块在之前写mqtt连接时用到到,但是当时不知其所以然。

 

 

Winform中使用MQTTnet实现MQTT的服务端和客户端之间的通信以及将订阅的消息保存到文件:

https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/117739859

4、异步

同步和异步主要用于修饰方法。当一个方法被调用时,调用者需要等待该方法执行完毕并返回才能继续执行,

我们称这个方法是同步方法;当一个方法被调用时立即返回,并获取一个线程执行该方法内部的业务,

调用者不用等待该方法执行完毕,我们称这个方法为异步方法。

异步的好处在于非阻塞(调用线程不会暂停执行去等待子线程完成),因此我们把一些不需要立即使用结果、

较耗时的任务设为异步执行,可以提高程序的运行效率。net4.0在ThreadPool的基础上推出了Task类,

微软极力推荐使用Task来执行异步任务,现在C#类库中的异步方法基本都用到了Task;net5.0推出了async/await,

让异步编程更为方便。

在C#5.0中出现的 async和await ,让异步编程变得更简单。

关于async Task 和await的使用不再详述,具体可自行学习。

下面参考一个网络上的示例

class Program
    {
        static void Main(string[] args)
        {
            string content = GetContentAsync(Environment.CurrentDirectory + @"/test.txt").Result;
            //调用同步方法
            //string content = GetContent(Environment.CurrentDirectory + @"/test.txt");
            Console.WriteLine(content);
            Console.ReadKey();
        }
        //异步读取文件内容
        async static Task<string> GetContentAsync(string filename)
        {
           
            FileStream fs = new FileStream(filename, FileMode.Open);
            var bytes = new byte[fs.Length];
            //ReadAync方法异步读取内容,不阻塞线程
            Console.WriteLine("开始读取文件");
            int len = await fs.ReadAsync(bytes, 0, bytes.Length);
            string result = Encoding.UTF8.GetString(bytes);
            return result;
        }
        //同步读取文件内容
        static string GetContent(string filename)
        {
            FileStream fs = new FileStream(filename, FileMode.Open);
            var bytes = new byte[fs.Length];
            //Read方法同步读取内容,阻塞线程
            int len =  fs.Read(bytes, 0, bytes.Length);
            string result = Encoding.UTF8.GetString(bytes);
            return result;
        }
    }

优化代码

首先将HttpClient修改为单例模式,避免每次请求接口都去new

新建类

 

{

    class Global
    {

        private static string _lockFlag = "GlobalLock";

        private static Global _instance;


        //http请求客户端
        public HttpClient httpClient = new HttpClient();

        private Global()
        {

        }

        public static Global Instance
        {
            get
            {
                lock (_lockFlag)
                {
                    if (_instance == null)
                    {
                        _instance = new Global();
                    }
                    return _instance;
                }
            }
        }

    }
}

关于全局/单例的实现可以参考如下:

C#中全局作用域的常量、字段、属性、方法的定义与使用:

https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/102550025

这里就一个窗体,所以在窗体初始化后设置其响应时长,也可放在全局工具类的get方法中

        private void Form1_Load(object sender, EventArgs e)
        {
            //设置http连接超时时间
            Global.Instance.httpClient.Timeout = TimeSpan.FromSeconds(httpClientMaxResponseSeconds);
        }

然后改造请求Http接口的方法

        private async Task<string> requestGetHttpData(string apiUrl)
        {
            try {
                var originAddressUrl = apiUrl;
                //请求接口数据        
                var url = new Uri(originAddressUrl);
                string jsonResponse = await Global.Instance.httpClient.GetStringAsync(url);
                return jsonResponse;
            }
            catch (Exception exception) {

                textBox_log.AppendText(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + ":调用接口" + apiUrl + "异常:" + exception.Message);
                textBox_log.AppendText("\r\n");
                
                return null;
            }
        }

将方法添加async Task<string>,注意这里httpClient调用GetStringAsync方法前面加了await

然后定时器执行中的方法调用修改为

        private async Task convertPositionControl() {
            try
            {
                //获取接口数据
                var positionData = await requestGetHttpData(positionCalculateUrl);
                //http请求不到数据,啥也不干
                if (null == positionData)
                {
                    return;
                }//请求到数据,则进行数据处理
                else
                {
                   
                }
            }
            catch (Exception exception)
            {
                textBox_log.AppendText(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + ":获取接口出错:");
                textBox_log.AppendText("\r\n");
                textBox_log.AppendText(exception.Message);
            }
        }

 

至此,不会导致界面卡住的现象。