c#中的超时终止

news2024/10/6 3:18:30

在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.coloradmin.cn/o/1904062.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C语言 指针和数组—指针数组及其在字符串处理中的应用

目录 问题的提出 问题的解决 回头看——指针、数组及其他类型的混合 指针数组与指向数组的指针 字符串的排序 问题的提出 问题的解决 回头看——指针、数组及其他类型的混合  基本数据类型  int 、 long 、 char 、 short 、 float 、 double……  数组是一种从…

001,函数指针是一种特殊的指针,它指向的是一个函数地址,可以存储函数并作为参数传递,也可以用于动态绑定和回调函数

函数指针是一种特殊的指针 001&#xff0c;函数指针是一种特殊的指针&#xff0c;它指向的是一个函数地址&#xff0c;可以存储函数并作为参数传递&#xff0c;也可以用于动态绑定和回调函数 文章目录 函数指针是一种特殊的指针前言总结 前言 这是ai回答的标准答案 下面我们…

SSM中小学生信息管理系统 -计算机毕业设计源码02677

摘要 随着社会的发展和教育的进步&#xff0c;中小学生信息管理系统成为学校管理的重要工具。本论文旨在基于SSM框架&#xff0c;采用Java编程语言和MySQL数据库&#xff0c;设计和开发一套高效、可靠的中小学生信息管理系统。中小学生信息管理系统以学生为中心&#xff0c;通过…

手把手搭建微信机器人,帮你雇一个24小时在线的个人 AI 助理(上)

上一篇&#xff0c;带领大家薅了一台腾讯云服务器&#xff1a;玩转云服务&#xff1a;手把手带你薅一台腾讯云服务器&#xff0c;公网 IP。 基于这台服务器&#xff0c;今天我们一起动手捏一个基于 LLM 的微信机器人。 0. 前置准备 除了自己常用的微信账号以外&#xff0c;还…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] LYA的生日聚会(100分) - 三语言AC题解(Python/Java/Cpp)

&#x1f36d; 大家好这里是清隆学长 &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f497; &#x1f…

【微服务】springboot对接Prometheus指标监控使用详解

目录 一、前言 二、微服务监控概述 2.1 微服务常用监控指标 2.2 微服务常用指标监控工具 2.3 微服务使用Prometheus监控优势 三、环境准备 3.1 部署Prometheus服务 3.2 部署Grafana 服务 3.3 提前搭建springboot工程 3.3.1 引入基础依赖 3.3.2 配置Actuator 端点 3.…

STM32实现看门狗(HAL库)

文章目录 一. 看门狗1. 独立看门狗&#xff08;IWDG&#xff09;1.1 原理1.2 相关配置1.3 相关函数 2. 窗口看门狗&#xff08;WWDG&#xff09;2.1 原理2.2 相关配置2.3 相关函数 一. 看门狗 单片机在日常工作中常常会因为用户配置代码出现BUG&#xff0c;而导致芯片无法正常工…

如何对GD32 MCU进行加密?

GD32 MCU有哪些加密方法呢&#xff1f;大家在平时项目开发的过程中&#xff0c;最后都可能会面临如何对出厂产品的MCU代码进行加密&#xff0c;避免产品流向市场被别人读取复制。 下面为大家介绍GD32 MCU所支持的几种常用的加密方法&#xff1a; 首先GD32 MCU本身支持防硬开盖…

无需服务器,浏览器跑700+AI模型?!【送源码】

Transformers.js 是一个创新的网络机器学习库&#xff0c;它将先进的 Transformer 模型直接带入浏览器&#xff0c;无需服务器端支持。这个库与 Hugging Face 的 Python transformers 库功能对等&#xff0c;提供相似的 API 接口来运行预训练模型&#xff0c;涵盖了自然语言处理…

人工智能系列-Python面向对象编程

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 面向对象编程和面向过程编程 在使用计算机语言进行代码编写时&#xff0c;常见的两种思路是面向对象编程和面向过程编程。 面向过程&#xff1a;根据业务逻辑从上到下写代码。…

Hi6602 恒压恒流SSR电源方案

Hi6602是一款针对离线式反激电源设计的高性能PWM控制器。Hi6602内集成有通用的原边恒流控制技术&#xff0c;可支持断续模式和连续模式工作&#xff0c;适用于恒流输出的隔离型电源应用中。Hi6602内部具有高精度65kHz开关频率振荡器&#xff0c;且带有抖频功能可优化EMI性能。H…

【Python迭代器探秘】:揭秘迭代器与生成器的魔法,掌握高效循环的艺术

文章目录 一、迭代器的基本概念1.1 迭代器优点1.2 迭代器的编写方法1.3 python内置迭代器函数1.4 小结1.5 迭代器对象与迭代对象1.5.1 区别1. 迭代对象2. 迭代器对象3. 小结 1.5.2 方法区分 二、生成器基本概念1. 生成器函数2. 生成器表达式 一、迭代器的基本概念 迭代器是Pyt…

【数据结构/操作系统 堆和栈】区别及应用场景、底层原理图解

堆和栈 比较有趣的是&#xff0c;计算机网络、操作系统中都会对堆栈有不同方面比较详细的描述&#xff0c;而使用的地方通常对这些底层的细节表现得没有那么明显。 但如果你能了解堆栈在计算机网络和操作系统中的表现形式&#xff0c;在你写代码时就会有不一样的认识&#xff…

基于AOP的数据字典实现:实现前端下拉框的可配置更新

作者&#xff1a;后端小肥肠 创作不易&#xff0c;未经允许严禁转载。 目录 1. 前言 2. 数据字典 2.1. 数据字典简介 2.2. 数据字典如何管理各模块的下拉框 3. 数据字典核心内容解读 3.1. 表结构 3.2. 核心代码 3.2.1. 根据实体类名称获取下属数据字典 3.2.2. 数据字…

【QT】显示类控件

显示类控件 显示类控件1. label - 标签2. LCD Number - 显示数字的控件3. ProgressBar - 进度条4. Calendar Widget - 日历5. Line Edit - 输入框6. Text Edit - 多行输入框7. Combo Box - 下拉框8. Spin Box - 微调框9. Date Edit & Time Edit - 日期微调框10. Dial - 旋钮…

3-4 优化器和学习率

3-4 优化器和学习率 主目录点这里 优化器是机器学习和深度学习模型训练过程中用于调整模型参数的方法。它的主要目标是通过最小化损失函数来找到模型参数的最优值&#xff0c;从而提升模型的性能。 在深度学习中&#xff0c;优化器使用反向传播算法计算损失函数相对于模型参数…

pycharm远程连接和conda环境参考博客自用整理

pycharm远程连接 pycharm的连接需要先用xftp把项目上传上去&#xff08;包括venv&#xff09;&#xff0c;似乎才能连 https://blog.csdn.net/weixin_41174300/article/details/134420981 注意要上传一份一模一样的&#xff0c;然后在deployment里面添加mapping 注意传输文件…

【C语言】操作符--百科全书

目录 一、操作符的分类 二、 ⼆进制和进制转换 三、 原码、反码、补码 四、 移位操作符 五、位操作符&#xff1a;&、|、^、~ 六、单⽬操作符 七、逗号表达式 八、 下标访问[]、函数调⽤() 九、结构体 十、操作符的属性&#xff1a;优先级、结合性 十一、表达式…

P1392 取数

传送门&#xff1a;取数 如若你看完题解后&#xff0c;仍有问题&#xff0c;欢迎评论 首先说一下 我首先想到的思路 &#xff08; 20%通过率 &#xff09;&#xff1a;通过dfs , 将所有的情况放入priority_queue中&#xff08;greater<int>&#xff09;&#xff0c;维持…

【ARMv8/v9 GIC 系列 1.7 -- GIC PPI | SPI | SGI | LPI 中断使能配置介绍】

文章目录 GIC 各种中断使能配置PPIs(每个处理器私有中断)SPIs(共享外设中断)SGIs(软件生成的中断)LPIs(局部中断)GIC 各种中断使能配置 在ARM GICv3和GICv4架构中,不同类型的中断(如PPIs、SPIs、SGIs和LPIs)可以通过不同的方式进行启用和禁用。 下面详细介绍这些中…