简介
在编写应用程序实现业务功能过程中,为解决吞吐量和响应效率的问题,我们会用到多线程、异步编程两项重要的技术。通过它们来提高应用程序响应和高效。应用程序每次运行都会启动一个进程(进程是一种正在执行的程序),而进程中可以包含一个或多个线程,由应用程序入口直接或间接执行的命令都由默认线程(或主线程)执行。
线程概述
线程是任务调度和执行的基本单位。线程是进程的一部分,线程共享该进程的资源。使用多线程技术可解决部分代码同时执行的需求,更好地利用资源。在介绍 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#的高级语法,其内容有很多,使用情况也很多。希望通过本篇对大家对线程后续的学习有帮助,敬请关注后续内容。