C#线程详解及应用示例

 简介

在编写应用程序实现业务功能过程中,为解决吞吐量和响应效率的问题,我们会用到多线程、异步编程两项重要的技术。通过它们来提高应用程序响应和高效。应用程序每次运行都会启动一个进程(进程是一种正在执行的程序),而进程中可以包含一个或多个线程,由应用程序入口直接或间接执行的命令都由默认线程(或主线程)执行。

线程概述

线程是任务调度和执行的基本单位。线程是进程的一部分,线程共享该进程的资源。使用多线程技术可解决部分代码同时执行的需求,更好地利用资源。在介绍 C# 线程相关技术点前,先梳理同步、异步、线程安全等几个概念。

1、同步是指多个线程严格按照顺序依次执行,当前线程执行完成后再执行下一个线程。

2、异步是指多个线程的执行顺序是不确定的,每个线程都独立执行,互不干扰。

3、线程安全是指多线程访问共享资源时,保证数据的一致性和完整性,避免出现数据竞争和不一致的结果。

4、争用条件是指多个线程共享访问同一数据时,每个线程都尝试操作该数据,从而导致数据被破坏。

实现方式

在C#语言中,可以通过 Thread 、ThreadPool、Task、Parallel 等类来实现多线程,根据具体特点和场景选择合适的方式来实现。

使用 Thread

通过Thread类实例化 Thread 对象是在构造方法中传入委托对象,Thread 类的构造方法的参数 ThreadStart(无参无返回值的委托)和 ParameterizedThreadStart(有一个object类型参数但无返回值的委托)两种。

通过示例来了解实现无参数的线程


using System;
using System.Threading;
using System.Windows.Forms;

namespace Fountain.WinForm.ThreadApp
{
  public partial class ThreadForm : Form
  {
    public ThreadForm()
    {
      InitializeComponent();
    }
    /// <summary>
    /// 显示时间
    /// </summary>
    public void DisplayTime()
    {
      try 
      {
        while (true)
        {
          this.LabelCurrentTime.Invoke(new EventHandler(delegate
          {
            // 访问主界面的控件
            this.LabelCurrentTime.Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
          }));
          Thread.Sleep(10);
        }
      }
      catch 
      {
      }
    }
    /// <summary>
    /// 
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void ThreadForm_Load(object sender, EventArgs e)
    {
      try
      {
        // 形式一
        Thread thread = new Thread(DisplayTime);
        // 设置为后台线程
        thread.IsBackground = true;
        // 开启线程
        thread.Start();
        // Lambda表达式代替方法DisplayTime
        Thread lambdaThread =new Thread(()=> 
        {
          this.LabelThreadId.Invoke(new EventHandler(delegate
          {
            // 访问主界面的控件
            this.LabelThreadId.Text = string.Format("Lambda代码段为执行的任务");
          }));
        });
         // 设置为后台线程
        lambdaThread.IsBackground = true;
        // 开启线程
        lambdaThread.Start();
      }
      catch
      {
      }
    }
  }
}

通过示例来了解实现带参数的线程

using System;
using System.IO;
using System.Threading;
using System.Windows.Forms;

namespace Fountain.WinForm.ThreadApp
{
  public partial class ThreadForm : Form
  {
    public ThreadForm()
    {
      InitializeComponent();
    }
    /// <summary>
    /// 删除日志文件
    /// </summary>
    public void Delete(object retentionTime)
    {
      // 日志目录
      string logDirectory = string.Format("{0}{1}", AppDomain.CurrentDomain.BaseDirectory, "Log");
      try
      {
        if (Directory.Exists(logDirectory))
        {
          // 获取所有匹配的日志文件
          string[] files = Directory.GetFiles(logDirectory, "*.log");
          // 遍历删除
          foreach (string file in files)
          {
            if (File.GetCreationTime(file).AddDays(Convert.ToInt32(retentionTime)) < DateTime.Now)
            {
              File.Delete(file);
            }
          }
        }
      }
      catch
      {
      }
    }
    /// <summary>
    /// 
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void ThreadForm_Load(object sender, EventArgs e)
    {
      try
      {
        //声明线程实例
        Thread parameterThread = new Thread(Delete);
        // 设置为后台线程
        parameterThread.IsBackground = true;
        // 开始执行线程,传递参数
        parameterThread.Start(10); 
      }
      catch
      {
      }
    }
  }
}

其它常用方法

方法 描述
Join() 阻塞调用线程,直到某个线程终止或执行完。
Suspend() 标记线程为挂起,进入暂停状态。
Resume() 继续已经挂起的线程。
Interrupt() 中断处于 WaitSleepJoin 状态的线程。
使用 ThreadPool

ThreadPool 是C# 提供的一个线程池,该线程池可用于执行任务、发送工作项、处理异步 I/O、代表其他线程等待以及处理计时器。

QueueUserWorkItem 方法

是用于将需要执行一个方法提交到线程池的队列。当线程池中有可用线程时,方法被执行。

通过示例解通过线程池的实现方式


using System;
using System.IO;
using System.Threading;
using System.Windows.Forms;
namespace Fountain.WinForm.ThreadApp
{
  public partial class ThreadPoolForm : Form
  {
    public ThreadPoolForm()
    {
        InitializeComponent();
    }
    /// <summary>
    /// 窗体加载
    /// </summary>
    private void ThreadPoolForm_Load(object sender, EventArgs e)
    {
      try
      {
        // 最大线程数
        int workerThreads = 0;
        // 异步 I/O 最大线程数
        int completionPortThreads = 0;
        // 
        ThreadPool.GetMaxThreads(out workerThreads, out completionPortThreads);
        this.LabelMaxThreads.Text = string.Format("{0}{1}", this.LabelMaxThreads.Text, workerThreads);
        // 启动线程
        ThreadPool.QueueUserWorkItem(new WaitCallback(Delete),10);
      }
      catch 
      {
      }
    }
    /// <summary>
    /// 删除日志文件
    /// </summary>
    public void Delete(object retentionTime)
    {
      // 日志目录
      string logDirectory = string.Format("{0}{1}", AppDomain.CurrentDomain.BaseDirectory, "Log");
      try
      {
        if (Directory.Exists(logDirectory))
        {
          // 获取所有匹配的日志文件
          string[] files = Directory.GetFiles(logDirectory, "*.log");
          // 遍历删除
          foreach (string file in files)
          {
            if (File.GetCreationTime(file).AddDays(Convert.ToInt32(retentionTime)) < DateTime.Now)
            {
              File.Delete(file);
            }
          }
        }
        this.LabelCompleted.Invoke(new EventHandler(delegate
        {
            // 访问主界面的控件
            this.LabelCompleted.Text = "线程执行完成";
        }));
      }
      catch
      {
      }
    }
  }
}

RegisterWaitForSingleObject 方法

是将指定的操作方法注册到线程池,在接收到事件处理器信号、指定等待的时间超时,辅助线程执行此操作的方法。

参数 描述
waitObject 注册等待 WaitHandle 的委托。用ManualResetEvent 或 AutoResetEvent
callback 在接收到事件处理器信号、指定等待的时间超时要执行的回调方法。
state 传递给回调方法的参数
millisecondsTimeOutInterval 等待的时间间隔,以毫秒为单位。0:立即返回,-1:无限等待。
executeOnlyOnce 指示回调是否只执行一次。

示例:每隔1秒刷新界面时间


using System;
using System.IO;
using System.Threading;
using System.Windows.Forms;
namespace Fountain.WinForm.ThreadApp
{
  public partial class ThreadPoolForm : Form
  {
    //创建一个AutoResetEvent,初始状态为未触发状态
    private AutoResetEvent autoResetEvent = new AutoResetEvent(false);
    /// <summary>
    /// 
    /// </summary>
    public ThreadPoolForm()
    {
      InitializeComponent();
    }
    /// <summary>
    /// 
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void ThreadPoolForm_Load(object sender, EventArgs e)
    {
      try
      {
        object state = new object();
        // 每隔1秒刷新界面时间
        ThreadPool.RegisterWaitForSingleObject(autoResetEvent,new WaitOrTimerCallback(TimerCallback), state, 1000,false);
      }
      catch 
      {
      }
    }
    /// <summary>
    /// 界面时间
    /// </summary>      
    private  void TimerCallback(object state, bool timedOut)
    {
      // 每隔1秒显示
      this.LabelCurrentTime.Invoke(new EventHandler(delegate
      {
          // 访问主界面的控件
          this.LabelCurrentTime.Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
      }));
    }
  }
}

关于ManualResetEvent 和 AutoResetEvent 的用法,后续展开。

使用 Task

Task 是C# (.NET 4.0)提供的一种简单和强大的异步处理类。除Dispose()之外所有成员都是线程安全的,并且可以同时从多个线程使用。

创建任务

1、通过构造函数

// 创建
Task task = new Task(()=>
{
// 界面显示时间
this.LabelCurrentTime.Invoke(new EventHandler(delegate
  {
// 访问主界面的控件
this.LabelCurrentTime.Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
  }));
});
// 启动
task.Start();
// 定义一个任务
Action<object> action = (object retentionTime) =>
{
  Delete(retentionTime);
};
// 创建
Task deleteTask = new Task(action, 10);
// 启动
deleteTask.Start();

2、使用 TaskFactory.StartNew 方法

// 创建
Task taskFactory = Task.Factory.StartNew(() => {
  // 界面显示时间
  this.LabelCurrentTime.Invoke(new EventHandler(delegate
  {
    // 访问主界面的控件
    this.LabelCurrentTime.Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
  }));
});
启动
taskFactory.Start();

3、使用 Run 方法 .NET 4.5之后的版本

// 创建并启动
Task.Run(() => 
{
  // 显示时间
  this.LabelCurrentTime.Invoke(new EventHandler(delegate
  {
    // 访问主界面的控件
    this.LabelCurrentTime.Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
  }));
});
使用 Parallel

Parallel 是 C# (.NET 4.0) 提供的对并行循环和区域的支持。其提供三个静态方法作为结构化并行的基本形式。

Invoke:并行调用多个任务

/// <summary>
/// 显示时间
/// </summary>
public void DisplayTime()
{
}
/// <summary>
/// 删除日志文件
/// </summary>
private void Delete()
{
}
/// <summary>
/// 按钮事件
/// </summary>
private void ButtonTaskk_Click(object sender, EventArgs e)
{
    Parallel.Invoke(DisplayTime, Delete);
}

For:循环执行并行方法

Parallel.For(0, 20, i =>
{
  this.TextBoxResult.BeginInvoke(new EventHandler(delegate
  {
    // 访问主界面的控件
    this.TextBoxResult.Text += string.Format("执行次数:{0},开始时间:{1}{2}", i, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), Environment.NewLine);
  }));
});

执行并行方法

List<string> weekend = new List<string>();
weekend.Add("星期日");
weekend.Add("星期一");
weekend.Add("星期二");
weekend.Add("星期三");
weekend.Add("星期四");
weekend.Add("星期五");
weekend.Add("星期六");

Parallel.ForEach(weekend,day =>
{
  this.TextBoxResult.BeginInvoke(new EventHandler(delegate
  {
    // 访问主界面的控件
    this.TextBoxResult.Text += string.Format("{0}{1}", day, Environment.NewLine);
  }));
});

小结

以上都是线程相关的内容,线程属于C#的高级语法,其内容有很多,使用情况也很多。希望通过本篇对大家对线程后续的学习有帮助,敬请关注后续内容。