c#中的超时终止

在C#中,可以使用CancellationTokenTask的超时机制来实现调用方法时的超时终止。

用Task.Delay(int)模拟耗时操作

        static async Task Main(string[] args)
        {
            using (var cts = new CancellationTokenSource(1 * 1000))
            {
                await doSomething(cts.Token);
            }


            Console.WriteLine("Press any key to end...");
            Console.ReadLine();
            Console.WriteLine();
        }

        static async Task doSomething(CancellationToken cancellationToken)
        {
            try
            {  
                int x = new Random().Next(4, 7);
                Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.fff")} 大约需要{x}秒才能执行结束");

                //模拟耗时操作
                await Task.Delay(x * 1000, cancellationToken); 


                Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.fff")} 执行doSomething结束");

            }
            catch (OperationCanceledException ex)
            {//如果被取消,则抛出异常
                Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.fff")} 超时了:" + ex.Message);
            }

        }

二 

        如果我们要设置超时的方法本身不支持异步或超时参数时,可以通过使用TaskCancellationToken来实现,但这需要一些间接的方式。       

        假设,方法doOperate(定义如下)执行时间较长,我们要在调用它时,设置超时操作。

 
    static bool doOperate(int x, string y, out string error)  
    {  
        // 模拟长时间操作  
        System.Threading.Thread.Sleep(5000); // 假设操作需要5秒  
        error = null; // 假设没有错误  
        Console.WriteLine($"Hello {y}");
        return true; // 假设操作成功  
    }  

        从doOperate的定义中,我们可以看到:doOperate方法并没有设计为异步且不接受超时或取消令牌。

        这种情况下,如果我们想设置在调用doOperate方法时超时,一种常用的方法是将doOperate的调用放在一个单独的任务中,并使用Task.DelayTask.WhenAny来等待doOperate或超时时间结束。

        这里我们可以使用Task.Run来封装doOperate的调用。

        但请注意,这可能会引入线程池的使用,如果doOperate是CPU密集型的操作,可能会影响系统性能。如果doOperate主要是IO操作(比如文件访问或数据库查询),这种方法的影响会比较小。


    static void Main(string[] args)
    {
        MethodX();
    }

    static void MethodX()
    {
        string error = null;
        bool result = CallDoOperateWithTimeout(3*1000, out error);

        if (!result)
        {
            Console.WriteLine($"Operation failed: {error ?? "Timeout occurred."}");
        }
        else
        {
            Console.WriteLine("Operation completed successfully.");
        }
    }

    static bool CallDoOperateWithTimeout(int timeoutMilliseconds, out string error)
    {
        var cts = new CancellationTokenSource(timeoutMilliseconds);
 
        string tempError = null;
        bool iResult = false;

        Task task = Task.Run(() =>
        {
            try
            {
                doOperate(42, "someInput", out tempError);
                iResult = true;
            }
            catch (Exception ex)
            {
                tempError = ex.Message;
                iResult = false;
            }
        }, cts.Token);

        try
        {
            task.Wait(cts.Token); // 等待任务完成或抛出异常  
            error = tempError;
            return iResult;
        }
        catch (OperationCanceledException)
        {
            error = "Operation timed out.";
            return false;
        }
        catch (Exception ex)
        {
            error = ex.Message;
            return false;
        }
    }

        执行后,我们会发现,确实提示”Operation failed: Operation timed out.“,但是也打印了”Hello somInput“——即doOperate方法并没有被终止掉,这是因为:

        doOperate 方法本身并不是真正的异步方法(即,它并没有使用 async 关键字,也没有 await 任何异步操作)。相反,它使用了 Thread.Sleep 来模拟长时间的操作,这是一个阻塞调用,会阻塞执行它的线程直到指定的时间过去。

        当我们从 Main 方法或任何其他同步上下文中启动这个 Task 时,虽然 Task 本身是在一个单独的线程上执行的,但 doOperate 方法内的 Thread.Sleep 会阻塞那个单独的线程。与此同时,Main 方法中的代码会继续执行到 task.Wait(cts.Token),它等待 Task 完成或超时。

        如果 Task(即 doOperate 方法的执行)在超时之前还没有完成(在这个例子中是5秒),CancellationTokenSource 的 Token 将会被触发来请求取消操作。但是,请注意,doOperate 方法内部并没有检查 CancellationToken 的状态,因此它不会提前退出。因此,Thread.Sleep 将会继续执行直到其完成,随后 doOperate 方法将输出 "Hello {y}" 并返回 true

        然而,在 Main 方法中,由于已经超过了超时时间,task.Wait(cts.Token) 会抛出一个 OperationCanceledException(或者,如果 Task 实际上在超时之后完成了,则不会抛出异常,但在这个例子中它不会)。这个异常会被捕获,并且错误消息会被设置为 "Operation timed out."

        要解决这个问题并让 doOperate 方法能够响应取消请求,您需要在 doOperate 方法内部定期检查 CancellationToken 的状态。

        但是,由于 doOperate 使用了 Thread.Sleep,而 Thread.Sleep 是不支持取消的,您需要使用其他方法来模拟异步操作,比如使用 Task.Delay(它可以接受一个 CancellationToken)或者实现您自己的异步逻辑(比如轮询某种条件或等待某个事件)。

        然而,在这个特定的例子中,由于 doOperate 方法是同步的并且使用了 Thread.Sleep,所以我们无法直接让它响应取消请求。

        如果我们想要让 doOperate 能够被取消,我们需要重写doOperate 以使用异步模式,或者找到一种方法来避免在需要响应取消的场景中使用 Thread.Sleep

        如果我们只是想在超时后停止等待 doOperate 的结果,并且不关心 doOperate 方法是否实际完成,那么我们的代码已经按预期工作了,只是 doOperate 会在后台继续执行直到完成。如果我们想要确保 doOperate 在超时后被取消(即停止执行),那么我需要重新设计 doOperate 方法以支持取消。

        重写doOperate 方法,将它转变为一个异步方法,并使用 CancellationToken 来检查是否应该提前退出。

        然而,由于原始的 doOperate 方法使用了 Thread.Sleep 来模拟长时间操作,这是不可取消的,我们需要找到一个替代方案。

一个常见的替代方案是使用 Task.Delay,它是一个可取消的异步延时操作。下面是重写后的 doOperate 方法和相应的调用逻辑:

    /// <summary>
    /// 封装操作结果的类
    /// </summary>
    private class OperationResult
    {
        /// <summary>
        /// 执行成功
        /// </summary>
        public bool Success { get; set; }

        /// <summary>
        /// 执行发生异常时的错误消息
        /// </summary>
        public string Error { get; set; }
    }

    /// <summary>
    /// 异步版本的doOperate方法
    /// </summary>
    /// <param name="x"></param>
    /// <param name="y"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    static async Task<OperationResult> doOperateAsync(int x, string y, CancellationToken cancellationToken)
    {
        try
        {
            // 使用Task.Delay模拟异步操作,该操作支持取消  
            await Task.Delay(5000, cancellationToken);

            // 假设这是耗时的异步操作  
            Console.WriteLine($"Hello {y}");

            return new OperationResult { Success = true, Error = null };
        }
        catch (OperationCanceledException)
        {
            // 如果操作被取消  
            return new OperationResult { Success = false, Error = "Operation was cancelled." };
        }
        catch (Exception ex)
        {
            // 捕获并处理其他可能的异常  
            return new OperationResult { Success = false, Error = ex.Message };
        }
    }

    static async Task Main(string[] args)
    {
        await MethodXAsync();

        Console.WriteLine("press any key to end..."); 
        Console.ReadKey();
    }

    static async Task MethodXAsync()
    {
        //设置超时时间是3秒
        OperationResult result = await CallDoOperateWithTimeoutAsync(3 * 1000);

        if (!result.Success)
        {
            Console.WriteLine($"Operation failed: {result.Error ?? "Timeout occurred."}");
        }
        else
        {
            Console.WriteLine("Operation completed successfully.");
        }
    }

    static async Task<OperationResult> CallDoOperateWithTimeoutAsync(int timeoutMilliseconds)
    {
        var cts = new CancellationTokenSource(timeoutMilliseconds);
        try
        {
            // 注意:这里我们不需要将cts.Token传递给Task.Run,  
            // 因为我们是在等待DoOperateAsync的完成,而不是Task.Run的完成。  
            // Task.Run主要用于在后台线程上执行代码,但在这里我们直接调用异步方法。  
            var result = await doOperateAsync(42, "someInput", cts.Token);
            return result;
        }
        catch (TaskCanceledException)
        {
            return new OperationResult { Success = false, Error = "Operation timed out." };
        }
        catch (Exception ex)
        {
            return new OperationResult { Success = false, Error = ex.Message };
        }
    }

请注意以下几点:

  1. 我将 Main 方法改为异步的,并使用了 await 关键字来等待 MethodXAsync 的完成。这是处理异步程序的常见做法。

  2. CallDoOperateWithTimeoutAsync 方法直接调用 doOperateAsync 并等待其完成,同时处理可能的取消异常和其他异常。doOperateAsync 方法现在是一个返回 OperationResult 实例的异步方法,该实例包含了操作的成功状态和可能的错误消息。

  3. DoOperateAsync 方法现在是一个异步方法,可以在不阻塞当前线程的情况下执行长时间的操作。
    如果 doOperateAsync 中的代码需要在另一个线程上执行(例如,因为它执行了阻塞的 I/O 操作),那么我们可以考虑使用 Task.Run 来封装这部分代码。但是,在这个例子中,Task.Delay 已经是一个异步操作,所以我们不需要额外的线程。

  4. 我创建了一个 OperationResult 类来封装 doOperate 方法的成功状态和可能的错误消息。这样,我们就可以在异步操作完成后返回一个包含这些信息的单一对象。

        现在,当我们运行这个程序时,如果 doOperateAsync 方法在超时之前完成,它将输出 "Hello someInput" 并报告成功。如果超时发生,它将报告超时错误,并且 doOperateAsync 方法中的 Console.WriteLine 将不会被执行(因为 Task.Delay 会被取消)。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/780865.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

leetcode每日一题-3101 交替子数组计数

暴力遍历&#xff1a;看起来像是回溯,实际上就是递归 class Solution { private:long long _res 0; public:long long countAlternatingSubarrays(vector<int>& nums) {backtrack(nums, 0);return _res;}void backtrack(vector<int>& nums, long long st…

黑马|最新AI+若依 |初识项目

本章主要内容是&#xff1a; 1.快速搭建了若依前后端项目在本地 2.实现了单表的增删改查快速生成 文章目录 介绍1.若依介绍2.若依的不同版本3.项目运行环境 初始化前后端项目1.下载若依项目2.初始化后端a.把表导入到数据库中b.更改application.yml文件 3.初始化前端a.安装依赖…

【游戏引擎之路】登神长阶(六)——雅达利2600汇编学习,任天堂居然还真不是抄袭起家

5月20日-6月4日&#xff1a;攻克2D物理引擎。 6月4日-6月13日&#xff1a;攻克《3D数学基础》。 6月13日-6月20日&#xff1a;攻克《3D图形教程》。 6月21日-6月22日&#xff1a;攻克《Raycasting游戏教程》。 6月23日-7月1日&#xff1a;攻克《Windows游戏编程大师技巧》。 7…

基于海思Hi3403V100方案开发双目1600万拼接相机测试截图

海思Hi3403V100平台SOC内置四核A55&#xff0c;提供高效且丰富和灵活的CPU资源&#xff0c;以满足客户计算和控制需求&#xff0c;并且集成单核MCU&#xff0c;已满足一些低延时要求较高场景。 多目相机PE108CB板是针对该芯片设计的一款多目凭借相机PCBA&#xff0c;硬件接口支…

node.js_HTTP协议

Hypertext Transfer Protocol 超文本传输协议 1.HTTP报文 请求行 请求头 请求体 它的内容形式很灵活&#xff0c;可以设置任意内容 2.HTTP响应报文 响应状态码 响应状态的描述 遇到陌生的状态码可以参考一下这个网址&#xff1a; https://developer.mozilla.org/zh-C…

期末成绩发布方式

期末考试结束后&#xff0c;成绩单的发放总是让老师们头疼不已。想象一下&#xff0c;每个学生的成绩都需要老师一个个私信给家长&#xff0c;不仅耗时耗力&#xff0c;而且极易出错。 在传统的成绩单发放方式中&#xff0c;老师往往需要通过电子邮件、短信或者微信等方式&…

python爬虫入门(一)之HTTP请求和响应

一、爬虫的三个步骤&#xff08;要学习的内容&#xff09; 1、获取网页内容 &#xff08;HTTP请求、Requests库&#xff09; 2、解析网页内容 &#xff08;HTML网页结构、Beautiful Soup库&#xff09; 3、存储或分析数据 b站学习链接&#xff1a; 【【Python爬虫】爆肝两…

数据合并 26-30题(30 天 Pandas 挑战)

数据合并 1. 知识点1.27 左连接1.28 数据填充与交叉连接1.29 获取列值列表 题目2.26 合作过至少三次的演员和导演2.27 使用唯一标识码替换员工ID2.28 学生们参加各科测试的次数2.29 至少有5名直接下属的经理2.30 销售员 1. 知识点 1.27 左连接 datapd.merge(employees,employ…

什么是五级流水?银行眼中的“好流水”,到底是什么样的?

无论是按揭买房还是日常贷款&#xff0c;银行流水都是绕不开的一环。规划好你的流水&#xff0c;不仅能让你在申请贷款时更有底气&#xff0c;还可能帮你省下不少冤枉钱。今天&#xff0c;咱们就来一场深度剖析&#xff0c;聊聊如何在按揭贷款、个人经营抵押贷款前&#xff0c;…

什么是SysTick?

一&#xff0c;滴答定时器SysTick SysTick&#xff0c;即滴答定时器&#xff0c;是内核中一个特殊的定时器&#xff0c;用于提供系统级的定时服务。是一个24位递减计时器&#xff0c;具有自动重载值寄存器的功能 。当计数器到达自动重载值时&#xff0c;它会自动重新加载新的计…

深入探索Python库的奇妙世界:赋能编程的无限可能

在编程的浩瀚宇宙中&#xff0c;Python以其简洁的语法、强大的功能和广泛的应用领域&#xff0c;成为了众多开发者心中的璀璨明星。而Python之所以能够如此耀眼&#xff0c;很大程度上得益于其背后庞大的库生态系统。这些库&#xff0c;如同一块块精心雕琢的积木&#xff0c;让…

【Linux详解】进程等待 | 非阻塞轮询

引入&#xff1a; 为什么&#xff1f;是什么&#xff1f;怎么办 是什么&#xff1f; 进程等待是指父进程暂停自己的执行&#xff0c;直到某个特定的子进程结束或发生某些特定的事件。 为什么&#xff1f; 僵尸进程刀枪不入&#xff0c;不可被杀死&#xff0c;存在内存泄露…

安卓备忘录App开发

安卓备忘录APP开发,文章末尾有源码和apk安装包 目标用户: 普通安卓手机用户,需要一个简单易用的备忘录App来记录和管理日常事务。 主要功能: 用户注册: 用户可以创建一个账号,输入用户名和密码。 用户登录: 用户可以通过用户名和密码登录到应用。 用户信息存储: 用户名和…

【python】OpenCV—Feature Detection and Matching

参考学习来自OpenCV基础&#xff08;23&#xff09;特征检测与匹配 文章目录 1 背景介绍2 Harris角点检测3 Shi-Tomasi角点检测4 Fast 角点检测5 BRIEF 特征描述子6 ORB(Oriented Fast and Rotated Brief) 特征描述子7 SIFT(Scale Invariant Feature Transform) 特征描述子8 SU…

从一个(模型设计的)想法到完成模型验证的步骤

从有一个大型语言模型&#xff08;LLM&#xff09;设计的想法到完成该想法的验证&#xff0c;可以遵循以下实践步骤&#xff1a; 需求分析&#xff1a; 明确模型的目的和应用场景。确定所需的语言类型、模型大小和性能要求。分析目标用户群体和使用环境。 文献调研&#xff1a…

【全面讲解下iPhone新机官网验机流程】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

实现多数相加,但是传的参不固定

一、情景 一般实现的加法和减法等简单的相加减函数的话。一般都是写好固定传的参数。比如&#xff1a; function add(a,b) {return a b;} 这是固定的传入俩个&#xff0c;如果是三个呢&#xff0c;有人说当然好办&#xff01; 这样写不就行了&#xff01; function add(a…

protobuf及其使用

首先打开proto文件&#xff0c;定义一个类&#xff08;数据结构&#xff09;&#xff0c;并编写成员变量 使用protobuf编译器protoc编译proto文件为.pb.h和.pb.c文件(c) 看绿色注释部分&#xff1a;从左至右为&#xff0c;编译器&#xff0c;.proto文件的路径&#xff0c;编译的…

YOLO V7网络实现细节(2)—网络整体架构总结

YOLO V7网络整体架构总结 YOLO v7网络架构的整体介绍 不同GPU和对应模型&#xff1a; ​​​​​​​边缘GPU&#xff1a;YOLOv7-tiny普通GPU&#xff1a;YOLOv7​​​​​​​云GPU的基本模型&#xff1a; YOLOv7-W6 激活函数&#xff1a; YOLOv7 tiny&#xff1a; leaky R…

微深节能 煤码头自动化翻堆及取料集控系统 格雷母线

微深节能格雷母线高精度位移测量系统是一种先进的工业自动化位置检测解决方案&#xff0c;它被广泛应用于煤码头自动化翻堆及取料集控系统中&#xff0c;以实现对斗轮堆取料机等大型机械设备的精准定位和自动化控制。 系统原理简述&#xff1a; 格雷母线系统的工作原理基于电磁…