• C#如何优雅地取消一个流程(非Thread.Abort方法)


    一. Thread.Abort() 的缺点

    我们使用 Thread.Abort() 来中止一个包裹着某个流程的线程,虽然 C# 并不会像 Thread.Suspend() 提示过时。但是在使用 Thread.Abort() 的时候,确实存在很多的问题:

    1. 该方式中止线程是通过在线程执行的时候抛出 ThreadAbortException 异常来实现的。这边抛出的 ThreadAbortException 异常,不一定可以被局部的程序异常处理程序准确地捕获,而会被抛出在全局,需要通过 AppDomain.CurrentDomain.UnhandledException 来进行捕获处理;

    注:关于 AppDomain.CurrentDomain.UnhandledException 的使用,请看下面的文章:

    《关于C# 全局异常捕获》

    2. 不能确定线程中止的时间和在中止之前所执行到的位置。总之,它取消线程是一个队列的处理方式,所以处理不是被立即响应的,而是要等一会,这就导致了程序的中止时间和在中止之前执行到了哪里都是未知的;

    二. 代替方法

    • 那么我们有没有什么方法来代替这种 Thread.Abort() 的方式呢?

    答:有。分析:中止一个流程,我们一定要向这个流程发送一个中止的信号,当这个流程获得这个信号的时候,立刻中断之后的操作并返回。因此,我们可以分析出来,这个流程在工作的时候,起码要有两个角色

    1. 一个是用来处理流程,也就是业务;
    2. 一个是用来处监视取消信号,并终止流程;

    综上,我们可以考虑使用委托的异步调用来实现,如果要取消就使用轮序信号来实现。

    三. 代码

    • 实现委托异步调用的类 LongTime.cs 代码:
    public class LongTime
    {
        public bool isComplete = false;
    
        public bool isCancel = false;
    
        public bool isSuccess = false;
    
        private object asynObject = new object();
    
        private bool LongTimeMethod()
        {
            lock (asynObject)
            {
                if(this.isCancel)
                {
                    //如果取消了要做什么操作
                }
    
                Thread.Sleep(10 * 1000);
                return false;
            }
        }
    
        public void Test()
        {
            Console.WriteLine("开始...");
    
            //异步执行体
            AsyncCallback callback = (r) =>
            {
                this.isComplete = r.IsCompleted;
            };
    
            IAsyncResult result = ((Action)(() =>
            {
                this.isSuccess = LongTimeMethod();
            }))
            .BeginInvoke(callback, null);
    
            //执行表征体
            while (!isCancel && !result.IsCompleted)
            {
                Thread.Sleep(50);
            }
    
            Console.WriteLine(this.isCancel ? "取消" : "完成");
        }
    
        /*
         * ❤重要❤
         * 会处在一个异步的线程中,如果这里面的内容与LongTimeMethod方法中的内容有冲突
         * 那么会导致线程先后的次序的问题
         * 所以这边要加上lock
         */
        public void Cancel()
        {
            lock (asynObject)
            {
                this.isCancel = true;
    
                //如果执行完成了业务逻辑之后的补救操作
            }
        }
    }
    • 上端调用测试代码:
    LongTime longTime = new LongTime();
    
    //随机取消
    Thread thread = new Thread(() =>
        {
            Thread.Sleep(2000);
            if ((new Random()).Next() % 2 == 0)
            {
                longTime.Cancel();
            }
        });
    thread.Start();
    
    //开始测试
    longTime.Test();
    
    Console.ReadKey();

    四. 要点分析

    • 我们需要注意什么呢?

    答:要注意的是在发送消息之后,更新、判断取消状态的线程异步问题。所以代码要上锁,如下:

    - 业务逻辑:

    private bool LongTimeMethod()
    {
        lock (asynObject)
        {
         if(this.isCancel)
            {
                //如果取消了要做什么操作
            }
    
            Thread.Sleep(10 * 1000);
            return false;
        }
    }

    - 取消操作:

    public void Cancel()
    {
        lock (asynObject)
        {
            this.isCancel = true;
    
            //如果执行完成了业务逻辑之后的补救操作
        }
    }

    由于我们无法知道,是 Cancel() 方法先修改 isCancel 标志位的值,还是 LongTimeMethod() 业务方法先判断 isCancel 为 false 并执行业务逻辑代码。所以程序在为后者的情况下,要在后执行的 Cancel() 方法中添加用于直接完成了业务逻辑的补救操作。

    • 示例程序,这是一个打开一个窗体程序,并取消前一个窗体之后再次打开一个窗体程序的代码示例

    - 窗体程序就是打开一个窗体,名字叫 " DemoForm.exe " 的程序

    - 实现异步委托调用的类,也就是业务逻辑类 LongTimeEx.cs,代码如下:

    public class LongTimeEx
    {
        public static bool isComplete = true;
    
        public static bool isCancel = true;
    
        public static bool isSuccess = false;
    
        private  static object asynObject = new object();
    
        private bool LongTimeMethod()
        {
            lock (asynObject)
            {
                if (isCancel)
                {
                    return false;
                }
    
                using (Process process = new Process())
                {
                    process.StartInfo.FileName = "DemoForm.exe";
                    process.Start();
                }
    
                return true;
            }
        }
    
        public void Test()
        {
            Console.WriteLine("开始...");
    
            //判断一下上一个是不是已经结束
            while (!LongTimeEx.isComplete || !LongTimeEx.isCancel)
            {
                Thread.Sleep(50);
            }
    
            //由于修改为了静态的变量 
            //导致这边每次都要重新刷新
            isComplete = false;
            isCancel = false;
    
            //异步执行体
            AsyncCallback callback = (r) =>
            {
                isComplete = r.IsCompleted;
            };
    
            IAsyncResult result = ((Action)(() =>
            {
                isSuccess = LongTimeMethod();
            }))
            .BeginInvoke(callback, null);
    
            //执行表征体
            while (!isCancel && !result.IsCompleted)
            {
                Thread.Sleep(50);
            }
        }
    
        public void Cancel()
        {
            lock(asynObject)
            {
                isCancel = true;
    
                //取消一下启动的进程
                Process[] process = Process.GetProcessesByName("DemoForm");
                if(process.Count()>0)
                {
                    foreach(Process p in process)
                    {
                        p.Kill();
                    }
                }
            }
        }
    }

    - 上端的调用代码:

    LongTimeEx longTimeEx = new LongTimeEx();
    longTimeEx.Test();
    longTimeEx.Cancel();
    longTimeEx.Test();
    
    Console.ReadKey();

    五. 示例代码下载

    下载地址

  • 相关阅读:
    5.4 省选模拟赛 修改 线段树优化dp 线段树上二分
    一本通 高手训练 1782 分层图 状压dp
    luogu P3830 [SHOI2012]随机树 期望 dp
    5.2 省选模拟赛 或许 线型基
    luogu P4562 [JXOI2018]游戏 组合数学
    一本通 高手训练 1781 死亡之树 状态压缩dp
    luogu P4726 【模板】多项式指数函数 多项式 exp 牛顿迭代 泰勒展开
    4.28 省选模拟赛 负环 倍增 矩阵乘法 dp
    HDU 1756 Cupid's Arrow 计算几何 判断一个点是否在多边形内
    一本通 高手训练 1763 简单树 可持久化线段树 树链刨分 标记永久化
  • 原文地址:https://www.cnblogs.com/Jeffrey-Chou/p/12256561.html
Copyright © 2020-2023  润新知