C#多线程和异步(一)——基本概念和使用方法
一、多线程相关的基本概念
进程(Process):是系统中的一个基本概念。 一个正在运行的应用程序在操作系统中被视为一个进程,包含着一个运行程序所需要的资源,进程可以包括一个或多个线程 。进程之间是相对独立的,一个进程无法访问另一个进程的数据(除非利用分布式计算方式),一个进程运行的失败也不会影响其他进程的运行,Windows系统就是利用进程把工作划分为多个独立的区域的。进程可以理解为一个程序的基本边界。
线程(Thread):是 进程中的基本执行单元,是操作系统分配CPU时间的基本单位 ,在进程入口执行的第一个线程被视为这个进程的 主线程 。
多线程能实现的基础:
1、CPU运行速度太快,硬件处理速度跟不上,所以操作系统进行分时间片管理。这样,宏观角度来说是多线程并发 ,看起来是同一时刻执行了不同的操作。但是从微观角度来讲,同一时刻只能有一个线程在处理。
2、目前电脑都是多核多CPU的,一个CPU在同一时刻只能运行一个线程,但是 多个CPU在同一时刻就可以运行多个线程 。
多线程的优点:
可以同时完成多个任务;可以让占用大量处理时间的任务或当前没有进行处理的任务定期将处理时间让给别的任务;可以随时停止任务;可以设置每个任务的优先级以优化程序性能。
多线程的缺点:
1、 内存占用 线程也是程序,所以线程需要占用内存,线程越多,占用内存也越多(每个线程都需要开辟堆栈空间,多线程时有时需要切换时间片)。
2、 管理协调 多线程需要协调和管理,所以需要占用CPU时间以便跟踪线程,线程太多会导致控制太复杂。
3、 资源共享 线程之间对共享资源的访问会相互影响,必须解决争用共享资源的问题。
二、C#中的线程使用
2.1 基本使用
2.1.1 无参时
class Program
{
static void Main(string[] args)
{
ThreadTest test = new ThreadTest();
//无参调用实例方法
Thread thread1 = new Thread(test.Func2);
thread1.Start();
Console.ReadKey();
}
} class ThreadTest
{
public void Func2()
{
Console.WriteLine("这是实例方法");
}
}
2.1.2 有参数时
class Program
{
static void Main(string[] args)
{
ThreadTest test = new ThreadTest();
//有参调用实例方法,ParameterizedThreadStart是一个委托,input为object,返回值为void
Thread thread1 = new Thread(new ParameterizedThreadStart(test.Func1));
thread1.Start("有参的实例方法");
Console.ReadKey();
}
}
class ThreadTest
{
public void Func1(object o)
{
Console.WriteLine(o);
}
}
2.2 常用的属性和方法
| 属性名称 | 说明 |
|---|---|
| CurrentThread | 获取当前正在运行的线程。 |
| ExecutionContext | 获取一个 ExecutionContext 对象,该对象包含有关当前线程的各种上下文的信息。 |
| IsBackground | bool,指示某个线程是否为后台线程。 |
| IsThreadPoolThread | bool,指示线程是否属于托管线程池。 |
| ManagedThreadId | int,获取当前托管线程的唯一标识符。 |
| Name | string,获取或设置线程的名称。 |
| Priority |
获取或设置一个值,该值指示线程的调度优先级 。 Lowest<BelowNormal<Normal<AboveNormal<Highest |
| ThreadState |
获取一个值,该值包含当前线程的状态。 Unstarted、Sleeping、Running 等 |
| 方法名称 | 说明 |
|---|---|
| GetDomain() | 返回当前线程正在其中运行的当前域。 |
| GetDomainId() | 返回当前线程正在其中运行的当前域Id。 |
| Start() | 执行本线程。(不一定立即执行,只是标记为可以执行) |
| Suspend() | 挂起当前线程,如果当前线程已属于挂起状态则此不起作用 |
| Resume() | 继续运行已挂起的线程。 |
| Interrupt() | 中断处于 WaitSleepJoin 线程状态的线程。 |
| Abort() | 终结线程 |
| Join() | 阻塞调用线程,直到某个线程终止。 |
| Sleep() | 把正在运行的线程挂起一段时间。 |
看一个简单的演示线程方法的栗子:

namespace ThreadForm
{
public partial class Form1 : Form
{ Thread thread;
int index = ;
public Form1()
{
InitializeComponent();
} //启动按钮
private void startBtn_Click(object sender, EventArgs e)
{
//创建一个线程,每秒在textbox中追加一下执行次数
if (thread==null)
{
thread = new Thread(() =>
{
while (true)
{
index++;
try
{
Thread.Sleep();
textBox1.Invoke(new Action(() =>
{
textBox1.AppendText($"第{index}次,");
}));
}
catch (Exception ex) { MessageBox.Show(ex.ToString()); }
}
});
//启动线程
thread.Start();
}
} //挂起按钮
private void suspendBtn_Click(object sender, EventArgs e)
{
if (thread != null && thread.ThreadState==ThreadState.Running || thread.ThreadState==ThreadState.WaitSleepJoin)
{
thread.Suspend();
}
} //继续运行挂起的线程
private void ResumeBtn_Click(object sender, EventArgs e)
{
if (thread!=null && thread.ThreadState==ThreadState.Suspended)
{
thread.Resume();
}
} //interrupt会报一个异常,并中断处于WaitSleepJoin状态的线程
private void InterruptBtn_Click(object sender, EventArgs e)
{
if (thread != null && thread.ThreadState==ThreadState.WaitSleepJoin)
{
thread.Interrupt();
}
}
//abort会报一个异常,并销毁线程
private void AbortBtn_Click(object sender, EventArgs e)
{
if (thread != null)
{
thread.Abort();
}
} //定时器,刷新显示线程状态
private void timer1_Tick(object sender, EventArgs e)
{
if (thread!=null)
{
txtStatus.Text = thread.ThreadState.ToString();
}
}
//窗体加载
private void Form1_Load(object sender, EventArgs e)
{
timer1.Interval = ;
timer1.Enabled = true;
}
//窗口关闭时,关闭进程
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
System.Diagnostics.Process[] processes = System.Diagnostics.Process.GetProcessesByName("ThreadForm");
foreach (var item in processes)
{
item.Kill();
}
}
}
}
当点击Start按钮,线程启动文本框会开始追加【第x次】字符串;点击Suspend按钮,线程挂起,停止追加字符串;点击Resume按钮会让挂起线程继续运行;点击Interrupt按钮弹出一个异常信息,线程状态从WaitSleepJoin变为Running,线程继续运行;点击Abort按钮会弹出一个异常信息并销毁线程。
一点补充:Suspend、Resume方法已不建议使用,推荐使用AutoResetEvent和ManualResetEvent来控制线程的暂停和继续,用法也十分简单,这里不详细介绍,有兴趣的小伙伴可以研究下。
2.3 线程同步
所谓同步: 是指在某一时刻只有一个线程可以访问变量 。
c#为同步访问变量提供了一个非常简单的方式,即使用c#语言的关键字Lock,它可以把一段代码定义为互斥段,互斥段在一个时刻内只允许一个线程进入执行,实际上是Monitor.Enter(obj),Monitor.Exit(obj)的语法糖。在c#中,lock的用法如下:
lock (obj) { dosomething... }
obj代表你希望锁定的对象,注意一下几点:
1. lock不能锁定空值 ,因为Null是不需要被释放的。 2. 不能锁定string类型 ,虽然它也是引用类型的。因为字符串类型被CLR“暂留”,这意味着整个程序中任何给定字符串都只有一个实例,具有相同内容的字符串上放置了锁,就将锁定应用程序中该字符串的所有实例。 3. 值类型不能被lock ,每次装箱后的对象都不一样 ,锁定时会报错 4 避免锁定public类型 如果该实例可以被公开访问,则 lock(this) 可能会有问题,因为不受控制的代码也可能会锁定该对象。
推荐使用 private static readonly类型的对象,readonly是为了避免lock的代码块中修改对象,造成对象改变后锁失效。
以书店卖书为例
class Program
{
static void Main(string[] args)
{
BookShop book = new BookShop();
//创建两个线程同时访问Sale方法
Thread t1 = new Thread(book.Sale);
Thread t2 = new Thread(book.Sale);
//启动线程
t1.Start();
t2.Start();
Console.ReadKey();
}
}
class BookShop
{
//剩余图书数量
public int num = ;
private static readonly object locker = new object();
public void Sale()
{ lock (locker)
{
int tmp = num;
if (tmp > )//判断是否有书,如果有就可以卖
{
Thread.Sleep();
num -= ;
Console.WriteLine("售出一本图书,还剩余{0}本", num);
}
else
{
Console.WriteLine("没有了");
}
}
}
}
代码执行结果时:

如果不添加lock则执行的结果时:

2.4 跨线程访问
例子:点击测试按钮,给文本框赋值

代码如下:
private void myBtn_Click(object sender, EventArgs e)
{
Thread thread1 = new Thread(SetValue);
thread1.Start(); }
private void SetValue()
{
for (int i = ; i < ; i++)
{
this.myTxtBox.Text = i.ToString();
}
}
执行代码会出现如下错误:

出现该错误的原因是:myTxtBox是由主线程创建的,thread1线程是另外一个线程,在.NET上执行的是托管代码, C#强制要求这些代码必须是线程安全的,即不允许跨线程访问Windows窗体的控件
解决的方法:
public Form1()
{
InitializeComponent();
}
//点击按钮开启一个新线程
private void myBtn_Click(object sender, EventArgs e)
{
Thread thread1 = new Thread(SetValues);
thread1.IsBackground = true;
thread1.Start();
} //新线程给文本框赋值
private void SetValues()
{
Action<int> setVal = (i) => { this.myTxtBox.Text = i.ToString(); };
for (int i = ; i < ; i++)
{
this.myTxtBox.Invoke(setVal, i);
}
}

Invoke:在“拥有控件的基础窗口句柄的线程” 即在本例的主线程上执行委托,这样就不存在跨线程访问了 ,因此还是线程安全的。
参考文章:
[1] https://www.cnblogs.com/dotnet261010/p/6159984.html
[2] https://www.cnblogs.com/wwj1992/p/5976096.html
C#多线程和异步(一)——基本概念和使用方法的更多相关文章
- 从Nginx的Web请求处理机制中剖析多进程、多线程、异步IO
Nginx服务器web请求处理机制 从设计架构来说,Nginx服务器是与众不同的.不同之处一方面体现在它的模块化设计,另一方面,也是最重要的一方面,体现在它对客户端请求的处理机制上. Web服务器和客 ...
- Android 多线程----AsyncTask异步任务详解
[声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/3 ...
- Android Learning:多线程与异步消息处理机制
在最近学习Android项目源码的过程中,遇到了很多多线程以及异步消息处理的机制.由于之前对这块的知识只是浅尝辄止,并没有系统的理解.但是工程中反复出现让我意识到这个知识的重要性.所以我整理出这篇博客 ...
- iOS多线程——同步异步串行并行
串行并行异步同步的概念很容易让人混淆,关于这几个概念我在第一篇GCD中有解释,但是还不够清晰,所以这里重写一篇博客专门对这几个概念进行区分: 先说一下队列和任务: (1)队列分为串行和并行,任务的执行 ...
- Task C# 多线程和异步模型 TPL模型 【C#】43. TPL基础——Task初步 22 C# 第十八章 TPL 并行编程 TPL 和传统 .NET 异步编程一 Task.Delay() 和 Thread.Sleep() 区别
Task C# 多线程和异步模型 TPL模型 Task,异步,多线程简单总结 1,如何把一个异步封装为Task异步 Task.Factory.FromAsync 对老的一些异步模型封装为Task ...
- C#基础之多线程与异步
1.基本概念 多线程与异步是两个不同概念,之所以把这两个放在一起学习,是因为这两者虽然有区别,但也有一定联系. 多线程是一个技术概念,相对于单线程而言,多线程是多个单线程同时处理逻辑.例如,假如说一个 ...
- C#多线程与异步
1.什么是异步同步 如果一个方法被调用,调用者需要等待该方法被执行完毕之后才能继续执行,则是同步. 如果方法被调用后立刻返回,即使该方法是一个耗时操作,也能立刻返回到调用者,调用者不需要等待该方法,则 ...
- async/await到底该怎么用?如何理解多线程与异步之间的关系?
前言 如标题所诉,本文主要是解决是什么,怎么用的问题,然后会说明为什么这么用.因为我发现很多萌新都会对之类的问题产生疑惑,包括我最初的我,网络上的博客大多知识零散,刚开始看相关博文的时候,就这样.然后 ...
- C#多线程的异步委托/调用
C#异步调用(Asynchronou Delegate) C#异步调用获取结果方法:主要有三种,也可以说是四种(官方说四种,电子书说三种),官方在MSDN上已经有详细的说明:链接 需要了解到获取异步执 ...
- 重新想象 Windows 8 Store Apps (44) - 多线程之异步编程: 经典和最新的异步编程模型, IAsyncInfo 与 Task 相互转换
[源码下载] 重新想象 Windows 8 Store Apps (44) - 多线程之异步编程: 经典和最新的异步编程模型, IAsyncInfo 与 Task 相互转换 作者:webabcd 介绍 ...
随机推荐
- elastic-search-kibana-in-docker-dotnet-core-app
[翻译] 使用ElasticSearch,Kibana,ASP.NET Core和Docker可视化数据 原文地址:http://www.dotnetcurry.com/aspnet/1354/e ...
- Centos wget命令 not found解决方法
如果已经有了yun源,则可通过yun源命令来安装wget. 如下所示: 2.yum安装yum -y install wget 即可安装:
- python3_列表、元组、集合、字典
列表list #列表的基本操作 >>> a=[] #创建空列表 >>> a = [0,1,2,3,4,5] #创建列表并初始化,列表是[]包含由逗号分隔的多个元素组 ...
- canvas高斯模糊算法
对于模糊图片这个效果的实现,其实css3中的filter属性也能够实现,但是这个属性的兼容性不是很好,所以我们通常不用这种方法实现,而使用canvas配合JS实现. <span style=&q ...
- hive外部表
创建外部表.数据从HDFS获取 只是建立了链接,hdfs中的数据丢失,表中数据也丢失;hdfs数据增加,表中数据也增加 上传文件 创建外部表 删除文件 执行查询语句,发现少了
- 在Delphi中调用"数据链接属性"对话框设置ConnectionString
项目需要使用"数据链接属性"对话框来设置ConnectionString,查阅了一些资料,解决办法如下: 1.Delphi 在Delphi中比较简单,步骤如下: 方法1: use ...
- 计算机cpu、寄存器、内存区别
1.寄存器是中央处理器内的组成部份.它跟CPU有关.寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令.数据和位址.在中央处理器的控制部件中,包含的寄存器有指令寄存器(IR)和程序计数器(PC). ...
- Linux管理用户和组
用户管理相关命令useradd 添加用户adduser 添加用户userdel 删除用户passwd 为用户设置密码usermod ...
- pgm12
作为 inference 部分的小结,我们这里对 machine learning 里面常见的三个 model 的 inference 问题进行整理,当然很幸运的是他们都存在 tractable 的算 ...
- BZOJ2017[USACO 2009 Nov Silver 1.A Coin Game]——DP+博弈论
题目描述 农夫约翰的奶牛喜欢玩硬币游戏,因此他发明了一种称为“Xoinc”的两人硬币游戏. 初始时,一个有N(5 <= N <= 2,000)枚硬币的堆栈放在地上,从堆顶数起的第I枚硬币的 ...