跨线程访问
1.需求

2.脚本
执行一个任务
csharp
//通过跨线程给控件赋值
private void btnExecute1_Click(object sender, EventArgs e)
{
Thread thread = new Thread(() =>
{
for (int i = 0; i < 10; i++)
{
// this.lblResult1.Text = i.ToString();
if (this.lblResult1.InvokeRequired)
{
this.lblResult1.Invoke(
new Action<string>(data => { this.lblResult1.Text = data; }),
i.ToString()
);
Thread.Sleep(300);
}
}
});
thread.IsBackground = true;
thread.Start();
}
通过线程读取值
csharp
private void btnRead_Click(object sender, EventArgs e)
{
Thread thread = new Thread(() =>
{
this.txtV.Invoke(new Action<string>(data =>
{
this.lblV.Text = data;//将读取的控件值,在其他控件中显示出来
}), this.txtV.Text);
});
thread.IsBackground = true;
thread.Start();
}
跨线程访问数据库
1.需求

2.脚本
执行任务一
csharp
private void btnExecute1_Click(object sender, EventArgs e)
{
Thread thread = new Thread(() =>
{
string classCount = DBUtility.SQLHelper.GetSingleResult("select count(*) from Products").ToString();
this.lblResult1.Invoke(new Action<string>(count => { this.lblResult1.Text = count; }), classCount);
});
thread.IsBackground = true;
thread.Start();
}
访问数据库
csharp
private void btnGetData_Click(object sender, EventArgs e)
{
Thread thread = new Thread(() =>
{
DataSet ds = DBUtility.SQLHelper.GetDataSet("select * from ProductInventory;select ProductId,ProductName,Unit from Products");
DataTable dt1 = ds.Tables[0];
DataTable dt2 = ds.Tables[1];
this.dgv1.Invoke(new Action<DataTable>(t => { this.dgv1.DataSource = t; }), dt1);
this.dgv2.Invoke(new Action<DataTable>(t => { this.dgv2.DataSource = t; }), dt2);
});
thread.IsBackground = true;
thread.Start();
}
3.sqlHelper
csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.IO;
namespace DBUtility
{
/// <summary>
/// 针对SQLServer数据库的通用访问类
/// </summary>
public class SQLHelper
{
//封装数据库连接字符串
private static string connString = "server=.;DataBase=SaleManagerDB;Uid=sa;Pwd=a123456";
public static object GetSingleResult(string sql)
{
SqlConnection conn = new SqlConnection(connString);
SqlCommand cmd = new SqlCommand(sql, conn);
try
{
conn.Open();
return cmd.ExecuteScalar();
}
catch (Exception ex)
{
//将异常信息写入日志 。。。
throw ex;
}
finally
{
conn.Close();
}
}
public static SqlDataReader GetReader(string sql)
{
SqlConnection conn = new SqlConnection(connString);
SqlCommand cmd = new SqlCommand(sql, conn);
try
{
conn.Open();
return cmd.ExecuteReader(CommandBehavior.CloseConnection);
}
catch (Exception ex)
{
conn.Close();
//将异常信息写入日志 。。。
throw ex;
}
}
public static DataSet GetDataSet(string sql)
{
SqlConnection conn = new SqlConnection(connString);
SqlCommand cmd = new SqlCommand(sql, conn);
SqlDataAdapter da = new SqlDataAdapter(cmd);//创建数据适配器对象
DataSet ds = new DataSet();//创建一个内存数据集
try
{
conn.Open();
da.Fill(ds);//使用数据适配器填充数据集
return ds;
}
catch (Exception ex)
{
//将异常信息写入日志 。。。
throw ex;
}
finally
{
conn.Close();
}
}
}
}
线程生命周期管理
1.需求

2.脚本
公共变量
csharp
private Thread thread = null;
private int counter = 0;
开启start
csharp
private void btnStart_Click(object sender, EventArgs e)
{
thread = new Thread(() =>
{
while (true)
{
try
{
Thread.Sleep(500);
lblInfo.Invoke(new Action(() =>
{
lblInfo.Text += counter++ + ",";
}));
}
catch (Exception ex)
{
MessageBox.Show(ex.Message + " 异常位置:" + counter++);
}
}
});
thread.Start();
}
暂停
csharp
private void btnSuspend_Click(object sender, EventArgs e)
{
if (thread.ThreadState == ThreadState.Running ||
thread.ThreadState == ThreadState.WaitSleepJoin)
{
thread.Suspend();
}
}
继续
csharp
private void btnResume_Click(object sender, EventArgs e)
{
if (thread.ThreadState == ThreadState.Suspended )
{
thread.Resume();
}
}
中断
csharp
private void btnInterrupt_Click(object sender, EventArgs e)
{
thread.Interrupt();
}
中止
csharp
private void btnAbort_Click(object sender, EventArgs e)
{
thread.Abort();
}
Join
csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace D02_3_ThreadJoinTest
{
class Program
{
//线程等待, task.wait()
static void Main(string[] args)
{
Thread thread = new Thread(new ThreadStart(() =>
{
Thread.Sleep(3000);
Console.WriteLine("这个是正在执行的子线程数据......");
}));
thread.Start();
thread.Join();//会等待子线程执行完毕后,在执行下面的主线程内容。
Console.WriteLine("这个是主线程的数据...");
Console.Read();
}
}
}
线程池
线程池ThreadPool的基本使用
csharp
static void Method11()
{
ThreadPool.QueueUserWorkItem((arg) =>
{
//请在这里编写实际的要处理的内容...
Console.WriteLine("子线程Id:" + Thread.CurrentThread.ManagedThreadId);
});
Console.WriteLine("主线程Id:" + Thread.CurrentThread.ManagedThreadId);
}
csharp
static void Method12()
{
ThreadPool.QueueUserWorkItem((arg) =>
{
//请在这里编写实际的要处理的内容...
Console.WriteLine("子线程Id:" + Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("arg=" + arg);
}, "com");
Console.WriteLine("主线程Id:" + Thread.CurrentThread.ManagedThreadId);
}
线程和ThreadPool和Thread性能比较
csharp
static void Method2()
{
for (int i = 1; i <= 10; i++)
{
Thread thread = new Thread(() =>
{
Console.WriteLine($"子线程{i} Id:" + Thread.CurrentThread.ManagedThreadId);
for (int a = 1; a <= 5; a++)
{
Console.WriteLine(a);
}
});
thread.Name = "线程名称:" + i;
thread.IsBackground = true;
thread.Start();
Thread.Sleep(50);
}
}
csharp
static void Method3()
{
Console.WriteLine("------------------------使用线程池----------------------------");
for (int i = 1; i <= 10; i++)
{
ThreadPool.QueueUserWorkItem((arg) =>
{
Console.WriteLine($"子线程{i} Id:" + Thread.CurrentThread.ManagedThreadId);
for (int a = 1; a <= 5; a++)
{
Console.WriteLine(a);
}
});
Thread.Sleep(50);
}
}
入口函数测试
csharp
static void Main(string[] args)
{
// Method11();
// Method12();
Method2();
// Method3();
Console.Read();
}
Task
Task使用【1】多线程任务的开启3种方式
csharp
//【1】通过new的方式创建一个Task对象,并启动
static void Method1_1()
{
Task task1 = new Task(() =>
{
//在这个地方编写我们需要的逻辑...
Console.WriteLine($"new一个新的Task启动的子线程Id={Thread.CurrentThread.ManagedThreadId}");
});
task1.Start();
}
csharp
//【2】使用Task的Run()方法
static void Method1_2()
{
Task task2 = Task.Run(() =>
{
//在这个地方编写我们需要的逻辑...
Console.WriteLine($"使用Task的Run()方法开启的子线程Id={Thread.CurrentThread.ManagedThreadId}");
});
}
csharp
//【3】使用TaskFactory启动(类似于ThreadPool)
static void Method1_3()
{
Task task3 = Task.Factory.StartNew(() =>
{
//在这个地方编写我们需要的逻辑...
Console.WriteLine($"使用TaskFactory开启的子线程Id={Thread.CurrentThread.ManagedThreadId}");
});
}
Task使用【2】Task的阻塞方式和任务延续
【1】回顾之前使用Thread多个子线程执行时阻塞的方法
csharp
static void Method2()
{
Thread thread1 = new Thread(() =>
{
Thread.Sleep(2000);
Console.WriteLine("Child Thread (1)......");
});
Thread thread2 = new Thread(() =>
{
Thread.Sleep(1000);
Console.WriteLine("Child Thread (2)......");
});
thread1.Start();
thread2.Start();
//...
thread1.Join();//让调用线程阻塞
thread2.Join();
//如果有很多的thread,是不是也得有很多的Join?还有,我们只希望其中一个执行完以后,后面的其他线程就能执行,这个也做不了!
Console.WriteLine("This is Main Thread!");
}
【2】Task各种【阻塞】方式(3个)
csharp
static void Method3()
{
Task task1 = new Task(() =>
{
Thread.Sleep(1000);
Console.WriteLine($"Task1子线程Id={Thread.CurrentThread.ManagedThreadId} {DateTime.Now.ToLongTimeString()}");
});
task1.Start();
Task task2 = new Task(() =>
{
Thread.Sleep(2000);
Console.WriteLine($"Task2子线程Id={Thread.CurrentThread.ManagedThreadId} {DateTime.Now.ToLongTimeString()}");
});
task2.Start();
////第1种方式:挨个等待和前面一样
//task1.Wait();
//task2.Wait();
////第2种方式:等待所有的任务完成 【推荐】
//Task.WaitAll(task1, task2);
//第3种方式:等待任何一个完成即可 【推荐】
Task.WaitAny(task1, task2);
Console.WriteLine("主线程开始运行!Time=" + DateTime.Now.ToLongTimeString());
}
Task任务的延续:WhenAll 希望前面所有任务执行完毕后,再继续执行后面的线程,和前面相比,既有阻塞,又有延续
csharp
static void Method4()
{
Task task1 = new Task(() =>
{
Thread.Sleep(1000);
Console.WriteLine($"Task1子线程Id={Thread.CurrentThread.ManagedThreadId} {DateTime.Now.ToLongTimeString()}");
});
task1.Start();
Task task2 = new Task(() =>
{
Thread.Sleep(2000);
Console.WriteLine($"Task2子线程Id={Thread.CurrentThread.ManagedThreadId} {DateTime.Now.ToLongTimeString()}");
});
task2.Start();
//线程的延续(主线程不等待,子线程依次执行,如果你需要主线程也按照子线程的顺序来,请你自己把主线程的任务放到延续任务中就可以)
Task.WhenAll(task1, task2).ContinueWith(task3 =>
{
//在这里可以编写你需要的业务...
Console.WriteLine($"Task3子线程Id={Thread.CurrentThread.ManagedThreadId} {DateTime.Now.ToLongTimeString()}");
});
Console.WriteLine("主线程开始运行!Time=" + DateTime.Now.ToLongTimeString());
}
Task的延续:WhenAny
csharp
static void Method5()
{
Task task1 = new Task(() =>
{
Thread.Sleep(1000);
Console.WriteLine($"Task1子线程Id={Thread.CurrentThread.ManagedThreadId} {DateTime.Now.ToLongTimeString()}");
});
task1.Start();
Task task2 = new Task(() =>
{
Thread.Sleep(2000);
Console.WriteLine($"Task2子线程Id={Thread.CurrentThread.ManagedThreadId} {DateTime.Now.ToLongTimeString()}");
});
task2.Start();
//线程的延续(主线程不等待,子线程任何一个执行完毕,就会执行后面的线程)
Task.WhenAny(task1, task2).ContinueWith(task3 =>
{
//在这里可以编写你需要的业务...
Console.WriteLine($"Task3子线程Id={Thread.CurrentThread.ManagedThreadId} {DateTime.Now.ToLongTimeString()}");
});
Console.WriteLine("主线程开始运行!Time=" + DateTime.Now.ToLongTimeString());
}
Task使用【3】Task常见枚举 TaskCreationOptions(父子任务运行、长时间运行的任务处理)
Task的构造方法,观察TaskCreationOptions这个枚举的类型,自己通过F12查看
csharp
static void Method6()
{
Task parentTask = new Task(() =>
{
Task task1 = new Task(() =>
{
Thread.Sleep(1000);
Console.WriteLine($"Task1子线程Id={Thread.CurrentThread.ManagedThreadId} {DateTime.Now.ToLongTimeString()}");
}, TaskCreationOptions.AttachedToParent);
Task task2 = new Task(() =>
{
Thread.Sleep(3000);
Console.WriteLine($"Task2子线程Id={Thread.CurrentThread.ManagedThreadId} {DateTime.Now.ToLongTimeString()}");
}, TaskCreationOptions.AttachedToParent);
task1.Start();
task2.Start();
});
parentTask.Start();
parentTask.Wait();//等待附加的子任务全部完成。相当于Task.WaitAll(taks1,task2);
//TaskCreationOptions.AttachedToParent如果这个枚举参数不添加,主线程会直接运行,不等待
Console.WriteLine("主线程开始执行!Time= " + DateTime.Now.ToLongTimeString());
}
长时间的任务运行,需要采取的方法
csharp
static void Method7()
{
Task task1 = new Task(() =>
{
Thread.Sleep(2000);
Console.WriteLine($"Task1子线程Id={Thread.CurrentThread.ManagedThreadId} {DateTime.Now.ToLongTimeString()}");
}, TaskCreationOptions.LongRunning);
//LongRunning:如果你明确知道这个任务是长时间运行的,建议你加上。当然你使用Thread也是可以的。但是不要使用
//ThreadPool,因为长时间占用不归还线程,系统会强制开启新的线程,会一定程度影响性能
task1.Start();
task1.Wait();
Console.WriteLine("主线程开始执行!Time= " + DateTime.Now.ToLongTimeString());
}
Task使用【4】Task中的取消功能:使用的是CacellationTokenSoure解决多任务中协作取消和超时取消方法
Task任务的取消和判断
csharp
static void Method8()
{
//创建取消信号源对象
CancellationTokenSource cts = new CancellationTokenSource();
Task task = Task.Factory.StartNew(() =>
{
while (!cts.IsCancellationRequested)//判断任务是否被取消
{
Thread.Sleep(200);
Console.WriteLine($"子线程Id={Thread.CurrentThread.ManagedThreadId} {DateTime.Now.ToLongTimeString()}");
}
}, cts.Token);
//我们在这个地方模拟一个事件产生
Thread.Sleep(2000);
cts.Cancel();//取消任务,只要传递这样一个信号就可以
}
【2】Task任务取消:同时我们也希望做一些清理的工作,也就是取消这个动作会触发一个任务
csharp
static void Method9()
{
CancellationTokenSource cts = new CancellationTokenSource();
Task task = Task.Factory.StartNew(() =>
{
while (!cts.IsCancellationRequested)
{
Thread.Sleep(500);
Console.WriteLine($"子线程Id={Thread.CurrentThread.ManagedThreadId} {DateTime.Now.ToLongTimeString()}");
}
}, cts.Token);
//注册一个委托:这个委托将在任务取消的时候调用
cts.Token.Register(() =>
{
//在这个地方可以编写自己要处理的逻辑...
Console.WriteLine("任务取消,开始清理工作......");
Thread.Sleep(2000);
Console.WriteLine("任务取消,清理工作结束......");
});
//这个地方肯定是有其他的逻辑来控制取消
Thread.Sleep(3000);//模拟其他的耗时工作
cts.Cancel();//取消任务
}
3】Task任务延时自动取消:比如我们请求一个远程接口,如果长时间没有返回数据,我们可以做一个时间限制,超时可以取消任务(比如微信红包退回)
csharp
static void Method10()
{
CancellationTokenSource cts = new CancellationTokenSource();
// CancellationTokenSource cts = new CancellationTokenSource(3000);
Task task = Task.Factory.StartNew(() =>
{
while (!cts.IsCancellationRequested)
{
Thread.Sleep(300);
Console.WriteLine($"子线程Id={Thread.CurrentThread.ManagedThreadId} {DateTime.Now.ToLongTimeString()}");
}
}, cts.Token);
//注册一个委托:这个委托将在任务取消的时候调用
cts.Token.Register(() =>
{
//在这个地方可以编写自己要处理的逻辑...
Console.WriteLine("任务取消,开始清理工作......");
Thread.Sleep(2000);
Console.WriteLine("任务取消,清理工作结束......");
});
cts.CancelAfter(3000); //3秒后自动取消
}
Task使用【5】Task中专门的异常处理:AggregateException
csharp
//AggregateException:是一个异常集合,因为Task中可能抛出异常,所以我们需要新的类型来收集异常对象
static void Method11()
{
var task = Task.Factory.StartNew(() =>
{
var childTask1 = Task.Factory.StartNew(() =>
{
//实际开发中这个地方写你处理的业务,可能会发生异常....
//自己模拟一个异常
throw new Exception("my god!Exception from childTask1 happend!");
}, TaskCreationOptions.AttachedToParent);
var childTask2 = Task.Factory.StartNew(() =>
{
throw new Exception("my god!Exception from childTask2 happend!");
}, TaskCreationOptions.AttachedToParent);
});
try
{
try
{
task.Wait(); //1.异常抛出的时机
}
catch (AggregateException ex) //2.异常所在位置
{
foreach (var item in ex.InnerExceptions)
{
Console.WriteLine(item.InnerException.Message + " " + item.GetType().Name);
}
//3.异常集合,如果你想往上抛,需要使用Handle方法处理一下
ex.Handle(p =>
{
if (p.InnerException.Message == "my god!Exception from childTask1 happend!")
return true;
else
return false; //返回false表示往上继续抛出异常
});
}
}
catch (Exception ex)
{
Console.WriteLine("-----------------------------------------------------");
Console.WriteLine(ex.InnerException.InnerException.Message);
}
}
监视锁:Lock 限制线程个数的一把锁
为什么要用锁?在多线程中,尤其是静态资源的访问,必然会有竞争
csharp
//为什么要用锁?在多线程中,尤其是静态资源的访问,必然会有竞争
private static int nums = 0;
private static object myLock = new object();
static void Method12()
{
for (int i = 0; i < 5; i++)
{
Task.Factory.StartNew(() =>
{
TestMethod();
});
}
}
static void TestMethod()
{
for (int i = 0; i < 100; i++)
{
lock (myLock)
{
nums++;
Console.WriteLine(nums);
}
}
}
//Lock是Monitor语法糖,本质是解决资源的锁定问题
//我们锁住的资源一定是让线程可访问到的,所以不能是局部变量。
//锁住的资源千万不要是值类型。
//lock也不能锁住string类型。
- 原因一:解决多线程并发访问共享资源的冲突问题
- 在多线程环境下,多个线程可能同时访问和修改共享资源(如全局变量、静态变量、对象的实例字段等)。如果没有适当的同步机制,就可能导致数据不一致或不可预期的结果。例如,假设有一个银行账户类,有一个
Balance属性表示账户余额,还有一个Withdraw方法用于取款。
- 在多线程环境下,多个线程可能同时访问和修改共享资源(如全局变量、静态变量、对象的实例字段等)。如果没有适当的同步机制,就可能导致数据不一致或不可预期的结果。例如,假设有一个银行账户类,有一个
csharp
class BankAccount
{
public decimal Balance { get; set; }
public void Withdraw(decimal amount)
{
Balance -= amount;
}
} class BankAccount
{
public decimal Balance { get; set; }
public void Withdraw(decimal amount)
{
Balance -= amount;
}
}
-
如果有两个线程同时调用
Withdraw方法,就可能出现问题。假设初始余额是100,两个线程都试图取出50。在没有同步的情况下,可能会出现这样的执行顺序:- 线程 1 读取
Balance的值为100。 - 线程 2 读取
Balance的值也为100。 - 线程 1 将
Balance更新为100 - 50 = 50。 - 线程 2 也将
Balance更新为100 - 50 = 50。
- 线程 1 读取
-
这样,最终余额是
50,而不是正确的0,这就是所谓的数据竞争。使用锁可以确保在一个线程访问共享资源时,其他线程必须等待,从而避免这种数据竞争。 -
原因二:保证代码块的原子性
- 原子操作是指不可被中断的一个或一系列操作。锁可以用来将一段代码标记为原子操作。例如,在一个复杂的计算过程中,可能涉及多个步骤来更新共享数据,这些步骤必须作为一个整体来执行,不能被其他线程干扰。
- 比如,有一个计数器,每次需要先读取当前值,然后进行加 1 操作,最后再保存回去。这三个步骤应该作为一个原子操作来执行。
csharp
class Counter
{
private int count;
public int Increment()
{
int temp;
lock (this)
{
temp = count;
count = temp + 1;
}
return count;
}
}
**lock**关键字的使用场景- 场景一:多个线程访问和修改同一个对象的状态时
- 当多个线程对同一个对象的属性或方法进行读写操作时,需要使用
lock。例如,在一个游戏中,有一个Player对象,它有Health(生命值)和Score(分数)属性。多个线程可能会同时更新玩家的生命值(例如受到伤害或恢复生命)和分数(例如完成任务或击杀敌人)。
- 当多个线程对同一个对象的属性或方法进行读写操作时,需要使用
- 场景一:多个线程访问和修改同一个对象的状态时
csharp
class Player
{
public int Health { get; set; }
public int Score { get; set; }
public void TakeDamage(int damage)
{
lock (this)
{
Health -= damage;
}
}
public void AddScore(int points)
{
lock (this)
{
Score += points;
}
}
}
- 场景二:共享资源的初始化过程
- 有些资源的初始化可能比较复杂且只需要执行一次,但可能会被多个线程同时访问。例如,一个数据库连接池,在应用程序启动时需要初始化连接池中的连接。
csharp
class DatabaseConnectionPool
{
private static DatabaseConnectionPool instance;
private List<DatabaseConnection> connections;
private static object lockObject = new object();
private DatabaseConnectionPool()
{
// 初始化连接池
connections = new List<DatabaseConnection>();
// 添加连接到连接池的代码省略
}
public static DatabaseConnectionPool Instance
{
get
{
if (instance == null)
{
lock (lockObject)
{
if (instance == null)
{
instance = new DatabaseConnectionPool();
}
}
}
return instance;
}
}
}
注释事项:
- 选择合适锁对象:
- 不能为私有变量
- 不能是字符串类型或者值类型,是对象
- 避免死锁 死锁是指两个或多个线程互相等待对方释放资源的情况。
- 例如,线程 A 持有锁 1,等待锁 2,而线程 B 持有锁 2,等待锁 1。为了避免死锁,要确保线程获取锁的顺序是一致的,并且尽量减少锁的嵌套和持有时间过长的情况。
跨线程UI空间
需求

脚本
异步更新数据
csharp
private void btnUpdate_Click(object sender, EventArgs e)
{
Task task = new Task(() =>
{
this.lblInfo.Text = "来自Task的数据更新:我们正在学习多线程!";
});
//task.Start(); //这样使用会报错
//使用下面的方式解决报错的问题
task.Start(TaskScheduler.FromCurrentSynchronizationContext());
}
耗时任务解决1
csharp
//针对UI耗时的情况,单独重载其实并不是很好
private void btnUpdate_Click1(object sender, EventArgs e)
{
Task task = new Task(() =>
{
//模拟耗时(这个地方会卡主)
Thread.Sleep(5000);
this.lblInfo.Text = "来自Task的数据更新:我们正在学习多线程!";
});
//task.Start(); //这样使用会报错
//使用下面的方式解决报错的问题
task.Start(TaskScheduler.FromCurrentSynchronizationContext());
}
耗时任务解决2
csharp
//针对耗时任务,我们可以使用新的方法
private void btnUpdate_Click2(object sender, EventArgs e)
{
this.btnUpdate.Enabled = false;
this.lblInfo.Text = "数据更新中,请等待......";
Task task =Task.Factory.StartNew(() =>
{
Thread.Sleep(5000); //有耗时的任务,我们可以放到ThreadPool中
});
//在ContinueWith中更新我们的数据
task.ContinueWith(t =>
{
this.lblInfo.Text = "来自Task的数据更新:我们正在学习多线程!";
this.btnUpdate.Enabled = true;
},TaskScheduler.FromCurrentSynchronizationContext()); //更新操作到同步的上下文中
}