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();

五. 示例代码下载

下载地址

原文地址:https://www.cnblogs.com/Jeffrey-Chou/p/12256561.html

时间: 2024-08-06 01:03:34

C#如何优雅地取消一个流程(非Thread.Abort方法)的相关文章

C# Thread.Abort方法与ThreadAbortException异常(取消线程与异常处理)

1.Abort当前线程,后续程序不会执行 class Program { public static Thread thread1; static void Main(string[] args) { thread1 = new Thread(Method1); thread1.Start(); Console.ReadKey(); } public static void Method1() { try { for (int i = 0; i < 10; i++) { Console.Writ

Retrofit2.0+ RxJava 优雅的取消重复避免并取消请求(十一)

Tamic/文 地址:http://blog.csdn.net/sk719887916/article/details/54575137 前几篇主要介绍了retrofit基本使用,结合rxJava的案列,以及RxJava结合retrofit的封装,包括公用参数,局部参数请求头添加,缓存,https, 文件上下传,结果解析,异常处理等,还有一些技巧,那么还有一个比较关键的是取消问题. 两者结合技巧可点击阅读:http://blog.csdn.net/sk719887916/article/deta

SylixOS 线程取消处理流程

概述取消一个线程要确保该线程能够释放其所持有的锁.分配的内存,使整个系统保持一致性.线程取消的途径有两种:异步取消和延时取消. 异步取消:被取消线程收到取消信号后,立即死亡.该线程缩持有的资源可能得不到释放,这是一种非安全的线程取消方式. 延时取消:系统默认取消方式,是一种比较安全的线程取消机制,被取消线程取消时,只是设置取消点(ptcbDel->TCB_bCancelRequest)为true,被取消线程继续运行,等到安全位置后进行取消操作. 取消点: 在使用延迟取消机制时,一个线程在可以被取

android源码解析(二十)--&gt;Dialog取消绘制流程

上几篇文章中我们分析了Dialog的加载绘制流程,也分析了Acvityi的加载绘制流程,说白了Android系统中窗口的展示都是通过Window对象控制,通过ViewRootImpl对象执行绘制操作来完成的,那么窗口的取消绘制流程是怎么样的呢?这篇文章就以Dialog为例说明Window窗口是如何取消绘制的. 有的同学可能会问前几篇文章介绍Activity的加载绘制流程的时候为何没有讲Activity的窗口取消流程,这里说明一下.那是因为当时说明的重点是Activity的加载与绘制流程,而取消绘

linux程序设计——取消一个线程(第十二章)

12.7    取消一个线程 有时,想让一个线程能够要求还有一个线程终止,就像给它发送一个信号一样. 线程有方法能够做到这一点,与与信号处理一样.线程能够被要求终止时改变其行为. pthread_cancel是用于请求一个线程终止的函数: #inlude <pthread.h> int pthread_cancel(pthread_t thread); 这个函数提供一个线程标识符就能够发送请求来取消它. 线程能够用pthread_setcancelstate设置线程的取消状态 #include

activiti 一个流程的运转步骤 以请假流程为例

---为了加深对activiti的理解记忆,对自己做的一个流程进行自述.加强记忆 请假实例 一.设计请假的流程图以及流程文件,完善对应数据项,比如用户信息,请假单信息 --请假单 --流程图 --流程文件leaveBill.bpmn <?xml version="1.0" encoding="UTF-8"?><definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL&qu

一个流程执行器的简单实现

/**  * 过程执行器  *  * @author leizhimin 2014/8/7 17:15  */ public class Test {     public static void main(String[] args) {         dotask(new Task(0));         System.out.println("------------------");         dotask(new Task(2));         System.o

华为 2015 机试 输出:数字后面的连续出现的(2个或多个)相同字符(数字或者字符),删去一个,非数字后面的不要删除,例如,对应输出为:33aabb55pin。

1 package 华为机试; 2 //C++ 输入:由数字和字母组成的字符串,例如:333aaabb55ppin 3 //输出:数字后面的连续出现的(2个或多个)相同字符(数字或者字符),删去一个,非数字后面的不要删除,例如,对应输出为:33aabb55pin. 4 5 //这句话的核心就是在字符串删除一些字符,感觉处理很复杂,删除哪些字符呢?我们观察发现, 本字符串中删除了一个3,一个a,一个p,满足的规则是啥呢? 333中删除最后一个3,3aa删除了一个a,5pp中删除一个p, 6 //规

【Activiti】为每一个流程绑定相应的业务对象的2种方法

方式1: 在保存每一个流程实例时,设置多个流程变量,通过多个流程变量的组合来过滤筛选符合该组合条件的流程实例,以后在需要查询对应业务对象所对应的流程实例时,只需查询包含该流程变量的值的流程实例即可. 设置过程:public void startProcess(Long id) { Customer cus = get(id); if (cus != null) { // 修改客户的状态 cus.setStatus(1); updateStatus(cus); // 将客户的追踪销售员的nickN