zoukankan      html  css  js  c++  java
  • 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();

    五. 示例代码下载

    下载地址

  • 相关阅读:
    js弹出文字
    javascript函数的使用
    php笔记-双引号内的变量会被解释,而单引号内的变量则原样输出
    单独编译源码树下的模块
    内核模块开机自动加载和黑名单
    [转]Linux中设置服务自启动的三种方式
    rpm打包
    APC to USB
    [转]创建一个虚拟盘
    编译打印输出重定向
  • 原文地址:https://www.cnblogs.com/Jeffrey-Chou/p/12256561.html
Copyright © 2011-2022 走看看