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

场景

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

Winform中怎样使用HttpClient调用http的get和post接口并将接口返回json数据解析为实体类_winform请求http接口_霸道流氓气质的博客-CSDN博客

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

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

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

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

Winform中使用mysqldump实现选择部分表定期备份mysql数据库_mysqldump 部分表_霸道流氓气质的博客-CSDN博客

注:

博客:
霸道流氓气质的博客_CSDN博客-C#,架构之路,SpringBoot领域博主

实现

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的服务端和客户端之间的通信以及将订阅的消息保存到文件:

Winform中使用MQTTnet实现MQTT的服务端和客户端之间的通信以及将订阅的消息保存到文件_mqtt winform_霸道流氓气质的博客-CSDN博客

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#中全局作用域的常量、字段、属性、方法的定义与使用:

C#中全局作用域的常量、字段、属性、方法的定义与使用_霸道流氓气质的博客-CSDN博客

这里就一个窗体,所以在窗体初始化后设置其响应时长,也可放在全局工具类的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);
            }
        }

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
.net C#线程超时的解决方案,使用候在被调线程入口调用一下这个方法就可以。更多详细代码见附件 Report.RegisterThread(Report.GetCurrentWin32ThreadID(),Thread.CurrentThread); #region 获取当取线程的ThreadID [DllImport("Kernel32", EntryPoint = "GetCurrentThreadId", ExactSpelling = true)] public static extern Int32 GetCurrentWin32ThreadID(); #endregion #region 登记访问任务子线程 /// /// 访问任务子线程 /// public static Hashtable TaskThreadIDTable = Hashtable.Synchronized(new Hashtable()); private static int[] TaskThreadIDs { get { int[] IDs = new int[TaskThreadIDTable.Keys.Count]; TaskThreadIDTable.Keys.CopyTo(IDs, 0); return IDs; } } public static void RegisterThread(int _threadid, System.Threading.Thread thread) { if (!TaskThreadIDTable.ContainsKey(_threadid)) TaskThreadIDTable.Add(_threadid, thread); if (!ExitInvalidThreadLoopRunning) { Thread ExitInvalidThreadLoopThread = new Thread(new ThreadStart(ExitInvalidThreadLoop)); ExitInvalidThreadLoopThread.Priority = ThreadPriority.AboveNormal; ExitInvalidThreadLoopThread.Start(); } } #endregion #region 关闭,退出超时的用户线程 private static DateTime ExitInvalidThreadLoop_LastRunTime = DateTime.Now.Subtract(new TimeSpan(1, 0, 0, 0, 0)); private static bool ExitInvalidThreadLoopRunning { get { return DateTime.Now.Subtract(ExitInvalidThreadLoop_LastRunTime).TotalMinutes 10) { try { Thread thread = (Thread)TaskThreadIDTable[t.Id]; thread.Abort(); } catch { } t.Dispose(); } } #endregion } #endregion

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

霸道流氓气质

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值