服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C# - C# 异步多线程入门到精通之Thread篇

C# 异步多线程入门到精通之Thread篇

2022-12-09 12:40菜鸟厚非 C#

Thread 是 1.0 时代的产物,当时 C# 就考虑到了多线程,于是就设计了 Thread 。其实现在不推荐使用,除非在维护老的项目已经用了的。Thread 也是比较鸡肋的,有很多缺陷,但也并不是一无是处

上一篇:C# 异步多线程入门基础
下一篇:C# 异步多线程入门到精通之ThreadPool篇

Thread API

这里对 Thread 的一些常用 API 进行介绍,使用一些案例进行说明。由于 Thread 的不可控与效率问题,Thread 现在已经不常用了,这里介绍一些 API ,想更深入的同学可以继续研究研究。

 

Instance

首先看 Thread 的构造函数,有 ThreadStart 、ParameterizedThreadStart 、maxStackSize 类型的参数,这三个常用的也就 ThreadStart ,其他两个可以作为了解。

C# 异步多线程入门到精通之Thread篇

分别 F12 查看 ThreadStart、ParameterizedThreadStart ,可以看到 ThreadStart 是无参数类型的委托、ParameterizedThreadStart 是有参数类型的委托。maxStackSize 是指定线程占用的最大内存数。

C# 异步多线程入门到精通之Thread篇

C# 异步多线程入门到精通之Thread篇

C# 异步多线程入门到精通之Thread篇

接着我们创建一个简单的案例,启动一个线程,模拟做一些任务行。如下代码

Console.WriteLine($"Main 方法开始,ThreadId:{Thread.CurrentThread.ManagedThreadId}");

ThreadStart threadStart = () =>
{
  Console.WriteLine($"Task Start ThreadId:{Thread.CurrentThread.ManagedThreadId}");
  // 做一些任务


  Console.WriteLine($"Task End ThreadId:{Thread.CurrentThread.ManagedThreadId}");
};
Thread thread = new Thread(threadStart);
thread.Start();

Console.WriteLine($"Main 方法结束,ThreadId:{Thread.CurrentThread.ManagedThreadId}");

Console.ReadLine();

C# 异步多线程入门到精通之Thread篇

启动程序,可以看到线程 1(主线程),没有等待线程 3(子线程)执行完成匿名方法内的任务,再执行 Main 结束这段代码。如果使用的是 winform 是不会卡界面的。

这就是异步多线程,异步在于线程 1 并没有等待线程 3 执行完成任务,再执行线程 1 内的下一行,而是让线程 3 在不影响线程 1 执行任务的情况下执行,这就是异步。多线程在于我们启动了一个线程 3(子线程),在 Main 方法由线程1(子线程)与线程 3(主线程)一起完成 Main 方法内的代码,这就是多线程。

C# 异步多线程入门到精通之Thread篇

说到委托可会有小伙伴发出疑问,为啥不用 Action ?

因为在这个版本还没有 Action、Func,这是在 .Net 3.0 时代的产物,Action、Func 的出现就是为了统一,也是为了解决此类问题。

在 dotnet 框架,也建议最好使用 Action、Func,所以,在这使用 Action 是不可以的。如下

Console.WriteLine($"Main 方法开始,ThreadId:{Thread.CurrentThread.ManagedThreadId}");

Action action = () =>
{
  Console.WriteLine($"Task Start ThreadId:{Thread.CurrentThread.ManagedThreadId}");
  // 做一些任务


  Console.WriteLine($"Task End ThreadId:{Thread.CurrentThread.ManagedThreadId}");
};

ThreadStart threadStart = action;

Thread thread = new Thread(threadStart);
thread.Start();

Console.WriteLine($"Main 方法结束,ThreadId:{Thread.CurrentThread.ManagedThreadId}");

Console.ReadLine();

C# 异步多线程入门到精通之Thread篇

 

Suspend、Resume

Suspend 挂起、Resume 唤醒,这两个是一对相互对应的 API,使用时这两个容易产生死锁,其实在实际中也是不应该使用的,.NET 框架已经抛弃了,说的很清楚了。

为什么会死锁呢?比如你开启了一个子线程 01,对 A 文件进行读写操作,此时你对子线程 01 进行了挂起。当你另外一个线程对 02 A 文件进行操作时,此时提示会 A 文件被占用,就行形成死锁。

C# 异步多线程入门到精通之Thread篇

C# 异步多线程入门到精通之Thread篇

 

Abort、ResetAbort

Abort 销毁,很多人在使用,这种是抛异常方式,使子线程销毁结束。这个功能也比较鸡肋,Abort 时子线程并不能立即停止,往往会有一些延迟,那这个销毁有时也不能达到我们可控的效果。

比如,在一个方法内开了一个子线程进行数据计算,但执行的时间太长了,我们等待了 5000 ms,此时 Abort 子线程,是不能立马让子线程停止计算,而是可能要等一会才能结束子线程。

比如,发出的动作,可能收不回来。查询数据库来说,当一个查库命令发送到数据库,我们在C# 执行了 Abort,但查库这个命令是收不回来的,因为他是在数据库层面,当数据库查询完成只是没有接收响应的线程罢了。

C# 异步多线程入门到精通之Thread篇

Abort 不建议使用,如果使用,一定要 try catch 一下。

Console.WriteLine($"Main 方法开始,ThreadId:{Thread.CurrentThread.ManagedThreadId}");

ThreadStart threadStart = () =>
{
  Console.WriteLine($"Task Start ThreadId:{Thread.CurrentThread.ManagedThreadId}");
  // 做一些任务

  Console.WriteLine($"Task End ThreadId:{Thread.CurrentThread.ManagedThreadId}");
};
Thread thread = new Thread(threadStart);

thread.Start();

try
{
  thread.Abort(); // 销毁,方式是抛异常,不一定及时
}
catch (Exception ex)
{
  //Thread.ResetAbort(); // 取消异常
}

Console.WriteLine($"Main 方法结束,ThreadId:{Thread.CurrentThread.ManagedThreadId}");

Console.ReadLine();

C# 异步多线程入门到精通之Thread篇

Suspend、Resume、Abort 这几个方法不建议使用,操作线程暂停、销毁或者其他操作都是不可控的,应为线程本身是操作系统的, CPU 分时分片会按照自己的规则进行运行,此时已经不是程序可以进行控的了。 既然设计了 Thread 不可能一无是处,接下来我们说些有用的

 

Join

线程等待 ,Join 可以一直等,也可以设置超时,超时就是等待一定时间,就不等了。等待的过程中主线程处于闲置状态等着子线程完成任务。如果是 winform 是会卡界面的,主线程等待也是一种工作。

例如:threadStart 我们模拟任务耗时 5 秒,在 thread.Start() 任务开始后,使用 thread.Join() 等着子线程完成工作

Console.WriteLine($"Main 方法开始,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");

ThreadStart threadStart = () =>
{
  Console.WriteLine($"Task Start ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");
  // 做一些任务
  Thread.Sleep(5 * 1000); // 模拟任务耗时 5 秒

  Console.WriteLine($"Task End ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");
};

Thread thread = new Thread(threadStart);
thread.Start();

thread.Join();

Console.WriteLine($"Main 方法结束,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");

Console.ReadLine();

启动程序,可以看到是我们想要的结果(与同步执行一样),主线程 1 一直等着 子线程 3 完成执行的任务。如果是 winform 是会卡界面的,虽然 thread.Join() 主线程 1 会等着子线程 3 完成工作,但主线程 1 等着也是一种工作。

C# 异步多线程入门到精通之Thread篇

接着我们看下超时等待,Join 的重载方法

例如:threadStart 我们模拟任务耗时 5 秒,在 thread.Start() 任务开始后,使用 thread.Join(3*1000) ,让主线程最多等子线程 3 秒,如果 3 秒子线程还未完成任务,就不等待了

Console.WriteLine($"Main 方法开始,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");

ThreadStart threadStart = () =>
{
  Console.WriteLine($"Task Start ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");
  // 做一些任务
  Thread.Sleep(5 * 1000); // 模拟任务耗时 5 秒

  Console.WriteLine($"Task End ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");
};

Thread thread = new Thread(threadStart);
thread.Start();

thread.Join(3 * 1000);

Console.WriteLine($"Main 方法结束,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");

Console.ReadLine();

启动程序,主线程 1 开始任务,子线程 3 也开始任务,当子线程执行 3 s 后(期间主线程 1 在等待),主线程 3 开始执行任务了。

C# 异步多线程入门到精通之Thread篇

注意:thread.Join(n * 1000) 并不是一定会等待那么长时间,而是最多等待,期间子线程任务执行完成后,就不等待了。

例如:threadStart 任务方法模拟 5 s,thread.Join(7 * 1000) 主线程等待 7 s

Console.WriteLine($"Main 方法开始,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");

ThreadStart threadStart = () =>
{
  Console.WriteLine($"Task Start ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");
  // 做一些任务
  Thread.Sleep(5 * 1000); // 模拟任务耗时 5 秒

  Console.WriteLine($"Task End ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");
};

Thread thread = new Thread(threadStart);
thread.Start();

thread.Join(7 * 1000);

Console.WriteLine($"Main 方法结束,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");

Console.ReadLine();

C# 异步多线程入门到精通之Thread篇

 

ThreadState

线程状态,ThreadState 也可以做线程等待,等待的过程中主线程处于闲置状态等着子线程完成任务。如果是 winform 是会卡界面的,主线程等待也是一种工作。

Console.WriteLine($"Main 方法开始,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");

ThreadStart threadStart = () =>
{
  Console.WriteLine($"Task Start ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");
  // 做一些任务
  Thread.Sleep(5 * 1000); // 模拟任务耗时 5 秒

  Console.WriteLine($"Task End ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");
};

Thread thread = new Thread(threadStart);
thread.Start();

while (thread.ThreadState != ThreadState.Stopped)
{
  Thread.Sleep(200); // 当前线程休息 200 毫秒
}

Console.WriteLine($"Main 方法结束,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");

Console.ReadLine();

C# 异步多线程入门到精通之Thread篇

 

Sleep

线程暂停,Sleep 当前线程暂停。如果是 winform 是会卡界面的,当 Sleep 时,CPU 分片就交出去了,主线程并不在工作状态。

Console.WriteLine($"Main 方法开始,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");

Thread.Sleep(5 * 1000); // 模拟任务耗时 5 秒

Console.WriteLine($"Main 方法结束,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");

Console.ReadLine();

C# 异步多线程入门到精通之Thread篇

 

IsBackground

是否是后台线程,当实例 Thread 时,默认是前台线程(IsBackground == false )。前台线程一定要任务完成,才会让进程退出。后台线程(IsBackground == true)会随着进程的结束而结束,无论子线程任务是否完成。

前台线程,意思也就是,当我们启动一个程序,当关闭程序时,如果还有子线程执行任务,当前进程是不会退出的,会等待着子进程将任务执行完成,也就是会阻止进程结束,反之亦然。

例如:前台线程,启动控制台后,主线程执行完任务后,会等待子线程任务完成(5s)后,窗口才会被关闭

static void Main(string[] args)
{
  Console.WriteLine($"Main 方法开始,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");

  ThreadStart threadStart = () =>
  {
      Console.WriteLine($"Task Start ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");
      // 做一些任务
      Thread.Sleep(5 * 1000); // 模拟任务耗时 5 秒

      Console.WriteLine($"Task End ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");
  };

  Thread thread = new Thread(threadStart);
  thread.Start();

  while (thread.ThreadState != ThreadState.Stopped)
  {
      Thread.Sleep(200); // 当前线程休息 200 毫秒
  }

  Console.WriteLine($"Main 方法结束,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");
}

例如:后台线程,启动控制台后,主线程任务执行完毕后,窗口会立马被关闭

static void Main(string[] args)
{
  Console.WriteLine($"Main 方法开始,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");

  ThreadStart threadStart = () =>
  {
      Console.WriteLine($"Task Start ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");
      // 做一些任务
      Thread.Sleep(5 * 1000); // 模拟任务耗时 5 秒

      Console.WriteLine($"Task End ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");
  };

  Thread thread = new Thread(threadStart);
  thread.IsBackground = true;
  thread.Start();

  Console.WriteLine($"thread IsBackground:{thread.IsBackground},DateTime:{DateTime.Now.ToLongTimeString()}");

  Console.WriteLine($"Main 方法结束,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");
}

 

Priority

线程可以设置优先级,当线程从高到低分配了优先级,在向 CPU 申请线程时会优先分配。但是这个功能也比较鸡肋,对于 CPU 而言,当他们同时过来,只是会为优先级高的先分进行分片,但优先级低的并不是不会分配,也不代表优先级高的就会先执行完成,这也取决执行的任务量。其实优先级也没什么用,多线程本来就是无序的。

Console.WriteLine($"Main 方法开始,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");

ThreadStart threadStart = () =>
{
  Console.WriteLine($"Task Start ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");
  // 做一些任务
  Thread.Sleep(5 * 1000); // 模拟任务耗时 5 秒

  Console.WriteLine($"Task End ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");
};

Thread thread = new Thread(threadStart);
thread.Priority = ThreadPriority.Highest;// CPU 会先执行,不代表 Highest 就最优先
thread.Start();

Console.WriteLine($"thread IsBackground:{thread.IsBackground},DateTime:{DateTime.Now.ToLongTimeString()}");

Console.WriteLine($"Main 方法结束,ThreadId:{Thread.CurrentThread.ManagedThreadId},DateTime:{DateTime.Now.ToLongTimeString()}");

 

总结

其实现在来说 ,1.0 时代的 Thread 已经没有什么优势,现在 Thread 唯一有意义的就是 IsBackground = false,这个前线程(前台线程会阻碍进程的退出),后续的多线程设计都是后台线程,没有前台线程这个功能设计。

到此这篇关于C# 异步多线程入门到精通之Thread篇的文章就介绍到这了,更多相关C# Thread内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://blog.csdn.net/weixin_46785144/article/details/121097918

延伸 · 阅读

精彩推荐
  • C#Dynamic和Var的区别及dynamic使用详解

    Dynamic和Var的区别及dynamic使用详解

    C#中的很多关键词用法比较容易混淆,var和dynamic就是其中一组,他们都可以申明动态类型的变量,但是本质上他们还是有不少区别的,下面通过本文给大家...

    JackWang-CUMT4522021-11-08
  • C#使用C#编写简单的图形化的可发送附件的邮件客户端程序

    使用C#编写简单的图形化的可发送附件的邮件客户端程序

    这篇文章主要介绍了使用C#编写一个图形化的可发送附件的邮件客户端程序的方法,文中的示例同样是基于支持smtp协议的邮件服务器,需要的朋友可以参考下...

    锋哥9762021-11-11
  • C#C# 修改文件的创建、修改和访问时间的示例

    C# 修改文件的创建、修改和访问时间的示例

    这篇文章主要介绍了C#实现修改文件的创建、修改和访问时间的示例,帮助大家更好的理解和学习使用c#,感兴趣的朋友可以了解下...

    我爱捣鼓4562022-11-13
  • C#C#如何快速释放内存的大数组详解

    C#如何快速释放内存的大数组详解

    这篇文章主要给大家介绍了关于C#如何快速释放内存的大数组的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习...

    林德熙4052022-10-28
  • C#UnityShader3实现转圈与冷却效果

    UnityShader3实现转圈与冷却效果

    这篇文章主要为大家详细介绍了UnityShader3实现转圈与冷却效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    宏哥19955702022-07-09
  • C#C# ManualResetEvent使用方法详解

    C# ManualResetEvent使用方法详解

    这篇文章主要为大家详细介绍了ManualResetEvent使用方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    田志良9262022-01-06
  • C#实例详解C#实现http不同方法的请求

    实例详解C#实现http不同方法的请求

    本篇文章给大家分享了C#实现http不同方法的请求的相关知识点以及实例代码,有需要的朋友参考下。...

    郝光明10752022-02-25
  • C#C# ODP.NET 调用Oracle函数返回值时报错的一个解决方案

    C# ODP.NET 调用Oracle函数返回值时报错的一个解决方案

    这篇文章主要介绍了C# ODP.NET 调用Oracle函数返回值时报错的一个解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习...

    邀月6142022-08-10