一、多线程相关的基本概念

进程(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#多线程和异步(一)——基本概念和使用方法的更多相关文章

  1. 从Nginx的Web请求处理机制中剖析多进程、多线程、异步IO

    Nginx服务器web请求处理机制 从设计架构来说,Nginx服务器是与众不同的.不同之处一方面体现在它的模块化设计,另一方面,也是最重要的一方面,体现在它对客户端请求的处理机制上. Web服务器和客 ...

  2. Android 多线程----AsyncTask异步任务详解

    [声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/3 ...

  3. Android Learning:多线程与异步消息处理机制

    在最近学习Android项目源码的过程中,遇到了很多多线程以及异步消息处理的机制.由于之前对这块的知识只是浅尝辄止,并没有系统的理解.但是工程中反复出现让我意识到这个知识的重要性.所以我整理出这篇博客 ...

  4. iOS多线程——同步异步串行并行

    串行并行异步同步的概念很容易让人混淆,关于这几个概念我在第一篇GCD中有解释,但是还不够清晰,所以这里重写一篇博客专门对这几个概念进行区分: 先说一下队列和任务: (1)队列分为串行和并行,任务的执行 ...

  5. 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 ...

  6. C#基础之多线程与异步

    1.基本概念 多线程与异步是两个不同概念,之所以把这两个放在一起学习,是因为这两者虽然有区别,但也有一定联系. 多线程是一个技术概念,相对于单线程而言,多线程是多个单线程同时处理逻辑.例如,假如说一个 ...

  7. C#多线程与异步

    1.什么是异步同步 如果一个方法被调用,调用者需要等待该方法被执行完毕之后才能继续执行,则是同步. 如果方法被调用后立刻返回,即使该方法是一个耗时操作,也能立刻返回到调用者,调用者不需要等待该方法,则 ...

  8. async/await到底该怎么用?如何理解多线程与异步之间的关系?

    前言 如标题所诉,本文主要是解决是什么,怎么用的问题,然后会说明为什么这么用.因为我发现很多萌新都会对之类的问题产生疑惑,包括我最初的我,网络上的博客大多知识零散,刚开始看相关博文的时候,就这样.然后 ...

  9. C#多线程的异步委托/调用

    C#异步调用(Asynchronou Delegate) C#异步调用获取结果方法:主要有三种,也可以说是四种(官方说四种,电子书说三种),官方在MSDN上已经有详细的说明:链接 需要了解到获取异步执 ...

  10. 重新想象 Windows 8 Store Apps (44) - 多线程之异步编程: 经典和最新的异步编程模型, IAsyncInfo 与 Task 相互转换

    [源码下载] 重新想象 Windows 8 Store Apps (44) - 多线程之异步编程: 经典和最新的异步编程模型, IAsyncInfo 与 Task 相互转换 作者:webabcd 介绍 ...

随机推荐

  1. Spring Cloud集成EDAS(替代Eureka)

    https://help.aliyun.com/document_detail/72618.html?spm=5176.7946893.821398.spring-cloud.603123beXemW ...

  2. JsTree使用一例

    SearchDesignPatent.treeContainer().jstree({ 'core' : { 'data' : json.data }, }).bind('click.jstree', ...

  3. dotnet core的下载地址 以及sdk和runtime的 version 简单说明

    1. dotnet core 2.1 的下载地址 https://dotnet.microsoft.com/download/dotnet-core/2.1 2. dotnet core 2.2 的下 ...

  4. 日常工作: 应用服务器Oracle驱动问题说明

    1. .net使用Oracle驱动的发展等 作为.net线的产品的ERP产品 最初的版本 使用了.net 1.1发布时 微软提供的Oracle的驱动 但是后续微软的.net产品线貌似没有继续发展相应的 ...

  5. Jquery Jquery对象和DOM对象的微妙联系

    声明变量 var  $variable=             Jquery 对象: var  varibake=             DOM对象: var $cr= $("#id&q ...

  6. python slots

    正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性.先定义class: >>> class Studen ...

  7. java程序在windows系统作为服务程序运行

    Java程序很多情况下是作为服务程序运行的,在Un*x 平台下可以利用在命令后加“&”把程序作为后台服务运行,但在Windows下看作那个Console窗口在桌面上,你是否一直担心别的同时把你 ...

  8. Java之StringBuffer使用方法

    package basic; //StringBuffer的使用方法,用于保存频繁修改的字符串 public class StringBufferDemo { public static void m ...

  9. Bootstrap面板

    前面的话 面板(Panels)是Bootstrap框架新增的一个组件,某些时候可能需要将某些 DOM 内容放到一个盒子里.对于这种情况,可以使用面板组件.本文将详细介绍Bootstrap面板 基础面板 ...

  10. LOJ121 动态图连通性(LCT)

    用LCT维护一下删除时间的最大生成树即可.当然也可以线段树分治. #include<iostream> #include<cstdio> #include<cmath&g ...