C#多线程
yuyutoo 2025-01-14 18:41 1 浏览 0 评论
1.概念
进程,线程,应用程序的定义网上有很多资料,但是有些抽象。通俗的来讲,进程就是 一旦一个应用程序开始运行,那么这个应用程序就会存在一个属于这个应用程序的进程。线程就是进程中的基本执行单元,每个进程中都至少存在着一个线程,这个线程是根据进程创建而创建的,所以这个线程我们称之为主线程。那么多线程就是包含有除了主线程之外的其他线程。如果一个线程可以执行一个任务,那么多线程就是可以同时执行多个任务。
2.C# 中的线程
Thread 类,Thread 类是用于控制线程的基础类,它存在于 System.Threading 命名空间。通过 Thread 可以控制当前应用程序域中线程的创建、挂起、停止、销毁。
Thread 一些常用属性:
Thread 一些常用方法:
Thread 的优先级:
3.多线程的基本示例
3.1 创建线程
创建一个控制台应用程序,
namespace MultiThreadDemo
{
class Program
{
static void Main(string[] args)
{
ThreadDemoClass demoClass = new ThreadDemoClass();
/////如下两种方法都可以创建线程,二选一即可
/////////////////创建线程 方法1 S
////通过类方法直接创建线程
//Thread thread = new Thread(demoClass.Run);
/////////////////创建线程 方法1 E
///////////////创建线程 方法2 S
//创建一个委托,并把要执行的方法作为参数传递给这个委托
ThreadStart threadStart = new ThreadStart(demoClass.Run);
Thread thread = new Thread(threadStart);
///////////////创建线程 方法2 E
//设置为后台线程
thread.IsBackground = true;
//开始线程
thread.Start();
////等待直到线程完成
//thread.Join();
Console.WriteLine("Main thread working...");
Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
Console.ReadKey();
}
}
public class ThreadDemoClass
{
public void Run()
{
Console.WriteLine("Child thread working...");
Console.WriteLine("Child thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
}
}
}
运行结果:
说明:
1)主线程创建了一个子线程并启动了它,但是主线程没有等到子线程执行完成,而是继续再往下执行的------线程异步或同步
2)如果要等子线程执行完成后再执行主线程-----通过线程的join()方法,方法不难,不单独展开
3)上面创建进程的方法没有带参数和返回值------参考3.2节
4)thread.IsBackground = true,即把当前线程设置为后台线程,因为使用 thread.Start() 启动的线程默认为前台线程。区别:前台线程就是系统会等待所有的前台线程运行结束后,应用程序域才会自动卸载。而设置为后台线程之后,应用程序域会在主线程执行完成时被卸载,而不会等待异步线程的执行完成。
3.2 创建带参数和返回值的线程
上面的这种使用多线程的方式只是简单的输出一段内容而已,多数情况下我们需要对线程调用的方法传入参数和接收返回值的,但是上面这种方法是不接受参数并且没有返回值的,那么我们可以使用 ParameterizedThreadStart 委托来创建多线程,这个委托可以接受一个 object 类型的参数,我们可以在这上面做文章,看如下示例,
3.2.1 一个参数
参考如下示例代码,
namespace MultiThreadDemo
{
class Program
{
static void Main(string[] args)
{
ThreadParameterDemoClass demoClass = new ThreadParameterDemoClass();
//创建一个委托,并把要执行的方法作为参数传递给这个委托
ParameterizedThreadStart threadStart = new ParameterizedThreadStart(demoClass.Run);
//创建一个新的线程
Thread thread = new Thread(threadStart);
//开始线程,并传入参数
thread.Start("shufac");
Console.WriteLine("Main thread working...");
Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
Console.ReadKey();
}
}
public class ThreadParameterDemoClass
{
public void Run(object obj)
{
string name = obj as string;
Console.WriteLine("Child thread working...");
Console.WriteLine("My name is " + name);
Console.WriteLine("Child thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
}
}
}
运行结果:
从上面的运行结果可以看到在多线程实现了参数的传递,可是它也只有一个参数,并且它接受的参数是 object 类型的(万类之源),也就是说既可以是值类型或引用类型,也可以是自定义类型。(当然,自定义类型其实也是属于引用类型的)下面我们使用自定义类型作为参数传递。
3.2.2 自定义类型参数传递
参考如下示例代码:
namespace MultiThreadDemo
{
class Program
{
static void Main(string[] args)
{
ThreadSelfDefParameterDemoClass demoClass = new ThreadSelfDefParameterDemoClass();
//创建一个委托,并把要执行的方法作为参数传递给这个委托
ParameterizedThreadStart threadStart = new ParameterizedThreadStart(demoClass.Run);
//创建一个新的线程
Thread thread = new Thread(threadStart);
UserInfo userInfo = new UserInfo();
userInfo.Name = "shufac";
userInfo.Age = 30;
//开始线程,并传入参数
thread.Start(userInfo);
Console.WriteLine("Main thread working...");
Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
Console.ReadKey();
}
}
public class ThreadSelfDefParameterDemoClass
{
public void Run(object obj)
{
UserInfo userInfo = (UserInfo)obj;
Console.WriteLine("Child thread working...");
Console.WriteLine("My name is " + userInfo.Name);
Console.WriteLine("I'm " + userInfo.Age + " years old this year");
Console.WriteLine("Child thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
}
}
public class UserInfo
{
public string Name { get; set; }
public int Age { get; set; }
}
}
运行结果:
3.2.3 带参数和返回值的线程
可以使用成员变量来试试 获取线程的返回值,参考如下示例代码,
class Program
{
List<UserInfo> userInfoList = new List<UserInfo>();
static void Main(string[] args)
{
Program program = new Program();
ParameterizedThreadStart threadStart = new ParameterizedThreadStart(program.Run);
Thread thread = null;
UserInfo userInfo = null;
for (int i = 0; i < 3; i++)
{
userInfo = new UserInfo();
userInfo.Name = "Brambling" + i.ToString();
userInfo.Age = 33 + i;
thread = new Thread(threadStart);
thread.Start(userInfo);
thread.Join();
}
foreach (UserInfo user in program.userInfoList)
{
Console.WriteLine("My name is " + user.Name);
Console.WriteLine("I'm " + user.Age + " years old this year");
Console.WriteLine("Thread ID is:" + user.ThreadId);
}
Console.ReadKey();
}
public void Run(object obj)
{
UserInfo userInfo = (UserInfo)obj;
userInfo.ThreadId = Thread.CurrentThread.ManagedThreadId;
userInfoList.Add(userInfo);
}
}
运行结果:
用上面这种方法勉强可以满足返回值的需求,但是却有很大的局限性,因为这里我使用的是成员变量,所以也就限制了线程调用的方法必须是在同一个类里面。
所以也就有了下面的方法,使用委托异步调用的方法(参考第3.4节------异步委托)。
小结:
1)传一个参数的方法,任何内置类型的数据应该都是可以的
2)使用自定义类型作为参数传递,理论上更多个参数也都是可以实现的
3)使用 ThreadStart 和 ParameterizedThreadStart 创建线程还是比较简单的,但是由于线程的创建和销毁需要耗费一定的开销,过多地使用线程反而会造成内存资源的浪费,从而影响性能,出于对性能的考虑,于是引入了线程池的概念。
3.3线程池
线程池并不是在 CLR 初始化的时候立刻创建线程的,而是在应用程序要创建线程来执行任务的时候,线程池才会初始化一个线程,初始化的线程和其他线程一样,但是在线程完成任务之后不会自行销毁,而是以挂起的状态回到线程池。当应用程序再次向线程池发出请求的时候,线程池里挂起的线程会再度激活执行任务。这样做可以减少线程创建和销毁所带来的开销。线程池建立的线程默认为后台线程。
示例代码:
namespace MultiThreadDemo
{
class Program
{
static void Main(string[] args)
{
ThreadPoolDemoClass demoClass = new ThreadPoolDemoClass();
//设置当没有请求时线程池维护的空闲线程数
//第一个参数为辅助线程数
//第二个参数为异步 I/O 线程数
ThreadPool.SetMinThreads(5, 5);
//设置同时处于活动状态的线程池的线程数,所有大于次数目的请求将保持排队状态,直到线程池变为可用
//第一个参数为辅助线程数
//第二个参数为异步 I/O 线程数
ThreadPool.SetMaxThreads(100, 100);
//使用委托绑定线程池要执行的方法(无参数)
WaitCallback waitCallback1 = new WaitCallback(demoClass.Run1);
//将方法排入队列,在线程池变为可用时执行
ThreadPool.QueueUserWorkItem(waitCallback1);
//使用委托绑定线程池要执行的方法(有参数)
WaitCallback waitCallback2 = new WaitCallback(demoClass.Run1);
//将方法排入队列,在线程池变为可用时执行
ThreadPool.QueueUserWorkItem(waitCallback2, "Brambling");
UserInfo userInfo = new UserInfo();
userInfo.Name = "Brambling";
userInfo.Age = 33;
//使用委托绑定线程池要执行的方法(有参数,自定义类型的参数)
WaitCallback waitCallback3 = new WaitCallback(demoClass.Run2);
//将方法排入队列,在线程池变为可用时执行
ThreadPool.QueueUserWorkItem(waitCallback3, userInfo);
Console.WriteLine();
Console.WriteLine("Main thread working...");
Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
Console.ReadKey();
}
}
public class ThreadPoolDemoClass
{
public void Run1(object obj)
{
string name = obj as string;
Console.WriteLine();
Console.WriteLine("Child thread working...");
Console.WriteLine("My name is " + name);
Console.WriteLine("Child thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
}
public void Run2(object obj)
{
UserInfo userInfo = (UserInfo)obj;
Console.WriteLine();
Console.WriteLine("Child thread working...");
Console.WriteLine("My name is " + userInfo.Name);
Console.WriteLine("I'm " + userInfo.Age + " years old this year");
Console.WriteLine("Child thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
}
}
public class UserInfo
{
public string Name { get; set; }
public int Age { get; set; }
}
}
运行结果:
使用线程池建立的线程也可以选择传递参数或不传递参数,并且参数也可以是值类型或引用类型(包括自定义类型)。看上面的结果发现了什么?没错,第一次执行的方法的线程ID为4,最后一次执行的方法的线程ID也为4。这就说明第一次请求线程池的时候,线程池建立了一个线程,当它执行完成之后就以挂起状态回到了线程池,在最后一次请求的时候,再次唤醒了该线程执行任务。这样就很容易理解了。
小结:线程池的引入主要解决频繁创建线程造成的内存资源的浪费,是个辅助功能,特殊的应用场景比较有用,用于提升性能。
3.4 异步委托
委托的异步调用有两个比较重要的方法:BeginInvoke() 和 EndInvoke()。
3.4.1 异步委托完成参数传递示例
参考如下示例代码:
class Program
{
//定义一个委托类
private delegate UserInfo MyDelegate(UserInfo userInfo);
static void Main(string[] args)
{
ThreadDemoClass demoClass = new ThreadDemoClass();
List<UserInfo> userInfoList = new List<UserInfo>();
UserInfo userInfo = null;
UserInfo userInfoRes = null;
//创建一个委托并绑定方法
MyDelegate myDelegate = new MyDelegate(demoClass.Run);
for (int i = 0; i < 3; i++)
{
userInfo = new UserInfo();
userInfo.Name = "Brambling" + i.ToString();
userInfo.Age = 33 + i;
//传入参数并执行异步委托
IAsyncResult result = myDelegate.BeginInvoke(userInfo,null,null);
//异步操作是否完成
while (!result.IsCompleted)
{
Thread.Sleep(100);
Console.WriteLine("Main thread working...");
Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
Console.WriteLine();
}
//结束异步委托,并获取返回值
userInfoRes = myDelegate.EndInvoke(result);
userInfoList.Add(userInfoRes);
}
foreach (UserInfo user in userInfoList)
{
Console.WriteLine("My name is " + user.Name);
Console.WriteLine("I'm " + user.Age + " years old this year");
Console.WriteLine("Thread ID is:" + user.ThreadId);
}
Console.ReadKey();
}
}
public class ThreadDemoClass
{
public UserInfo Run(UserInfo userInfo)
{
userInfo.ThreadId = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("Child thread working...");
Console.WriteLine("Child thread ID is:" + userInfo.ThreadId);
Console.WriteLine();
return userInfo;
}
}
执行结果:
BeginInvoke() 方法用于异步委托的执行开始,EndInvoke() 方法用于结束异步委托,并获取异步委托执行完成后的返回值。IAsyncResult.IsCompleted 用于监视异步委托的执行状态(true / false),这里的时间是不定的,也就是说一定要等到异步委托执行完成之后,这个属性才会返回 true。如果异步委托的方法耗时较长,那么主线程会一直工作下去。BeginInvoke() 是可以接受多个参数的,它的参数个数和参数类型取决于定义委托时的参数个数和类型,无论它有多少个参数,最后两个参数都是不变的,下面我们会说到。
3.4.2异步委托自定义指定单个线程等待时间
我们还可以用下面的方法 WaitOne(),自定义一个等待的时间,如果在这个等待时间内异步委托没有执行完成,那么就会执行 while 里面的主线程的逻辑,反之就不会执行。
class Program
{
//定义一个委托类
private delegate UserInfo MyDelegate(UserInfo userInfo);
static void Main(string[] args)
{
ThreadDemoClass demoClass = new ThreadDemoClass();
List<UserInfo> userInfoList = new List<UserInfo>();
UserInfo userInfo = null;
UserInfo userInfoRes = null;
//创建一个委托并绑定方法
MyDelegate myDelegate = new MyDelegate(demoClass.Run);
for (int i = 0; i < 3; i++)
{
userInfo = new UserInfo();
userInfo.Name = "Brambling" + i.ToString();
userInfo.Age = 33 + i;
//传入参数并执行异步委托
IAsyncResult result = myDelegate.BeginInvoke(userInfo,null,null);
//阻止当前线程,直到 WaitHandle 收到信号,参数为指定等待的毫秒数
while (!result.AsyncWaitHandle.WaitOne(1000))
{
Console.WriteLine("Main thread working...");
Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
Console.WriteLine();
}
//结束异步委托,并获取返回值
userInfoRes = myDelegate.EndInvoke(result);
userInfoList.Add(userInfoRes);
}
foreach (UserInfo user in userInfoList)
{
Console.WriteLine("My name is " + user.Name);
Console.WriteLine("I'm " + user.Age + " years old this year");
Console.WriteLine("Thread ID is:" + user.ThreadId);
}
Console.ReadKey();
}
}
public class ThreadDemoClass
{
public UserInfo Run(UserInfo userInfo)
{
userInfo.ThreadId = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("Child thread working...");
Console.WriteLine("Child thread ID is:" + userInfo.ThreadId);
Console.WriteLine();
return userInfo;
}
}
运行结果:
WaitOne() 方法只能用于监视当前线程的对象,如果要监视多个对象可以使用 WaitAny(WaitHandle[], int)或 WaitAll (WaitHandle[] , int) 这两个方法。
3.4.3异步委托自定义指定多个线程等待时间
WaitOne() 方法只能用于监视当前线程的对象,如果要监视多个对象可以使用 WaitAny(WaitHandle[], int)或 WaitAll (WaitHandle[] , int) 这两个方法。
参考如下示例代码,
class Program
{
//定义一个委托类
private delegate UserInfo MyDelegate(UserInfo userInfo);
static void Main(string[] args)
{
ThreadDemoClass demoClass = new ThreadDemoClass();
List<UserInfo> userInfoList = new List<UserInfo>();
UserInfo userInfo = null;
UserInfo userInfoRes = null;
//创建一个委托并绑定方法
MyDelegate myDelegate = new MyDelegate(demoClass.Run);
for (int i = 0; i < 3; i++)
{
userInfo = new UserInfo();
userInfo.Name = "Brambling" + i.ToString();
userInfo.Age = 33 + i;
//传入参数并执行异步委托
IAsyncResult result = myDelegate.BeginInvoke(userInfo,null,null);
IAsyncResult result1 = myDelegate.BeginInvoke(userInfo, null, null);
//定义要监视的对象,不能包含对同一对象的多个引用
WaitHandle[] waitHandles = new WaitHandle[] { result.AsyncWaitHandle, result1.AsyncWaitHandle };
while (!WaitHandle.WaitAll(waitHandles,1000))
{
Console.WriteLine("Main thread working...");
Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
Console.WriteLine();
}
//结束异步委托,并获取返回值
userInfoRes = myDelegate.EndInvoke(result);
userInfoList.Add(userInfoRes);
userInfoRes = myDelegate.EndInvoke(result1);
userInfoList.Add(userInfoRes);
}
foreach (UserInfo user in userInfoList)
{
Console.WriteLine("My name is " + user.Name);
Console.WriteLine("I'm " + user.Age + " years old this year");
Console.WriteLine("Thread ID is:" + user.ThreadId);
}
Console.ReadKey();
}
}
public class ThreadDemoClass
{
public UserInfo Run(UserInfo userInfo)
{
userInfo.ThreadId = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("Child thread working...");
Console.WriteLine("Child thread ID is:" + userInfo.ThreadId);
Console.WriteLine();
return userInfo;
}
}
运行结果:
WaitAll() 方法和 WaitAny() 方法都可以监视多个对象,不同的是 WaitAll() 方法需要等待所有的监视对象都收到信号之后才会返回 true,否则返回 false。而 WaitAny() 则是当有一个监视对象收到信号之后就会返回一个 int 值,这个 int 值代表的是当前收到信号的监视对象的索引。注意:在定义监视对象的时候,不能包含对同一个对象的多个引用,我这里是定义的两个示例,所以是不同的对象。
接下来我们看上面的执行结果。为什么主线程没有“工作”呢?这里你可以在异步委托调用的 Run() 方法里面为线程设置一个几秒钟或者更长地挂起时间。然后在设置监视对象这里设置一个小于线程挂起的时间,然后调试你就能发现问题的所在了。其实也不算是问题,只是之前的理解有误。
其实 WaitAll() 方法和 WaitAny() 方法设置监视对象,然后指定一个时间(毫秒值),这里的意思是当所有的监视对象在指定的时间内都接收到信号时(这里是指 WaitAll() 方法),就不会执行 while 里面的主线程的工作,反之就会执行。
这里你可能会有疑问,如果是这样,那我把前面的逻辑非运算符去掉那不就相反了么。这么理解逻辑上是没错的,但是我还是要说的是,尽量不要去尝试,因为这会是个死循环。WaitAny() 这个方法也是一样的理解,不同的是,它不需要等到所有的监视对象都收到信号,它只需要一个监视对象收到信号就够了,这里就不在演示了。
上面的方法可以看出,我们虽然使用的是异步的方式调用的方法,但是依旧需要等待异步的方法返回执行的结果,尽管我们可以不阻塞主线程,但是还是觉得不太方便。所以也就有了本篇博客最后一个要点,异步委托的回调函数。
3.4.4异步委托回调函数
示例代码如下,
class Program
{
//定义一个委托类
private delegate UserInfo MyDelegate(UserInfo userInfo);static void Main(string[] args)
{
ThreadDemoClass demoClass = new ThreadDemoClass();
UserInfo userInfo = null;
//创建一个委托并绑定方法
MyDelegate myDelegate = new MyDelegate(demoClass.Run);
//创建一个回调函数的委托
AsyncCallback asyncCallback = new AsyncCallback(Complete);
for (int i = 0; i < 3; i++)
{
userInfo = new UserInfo();
userInfo.Name = "Brambling" + i.ToString();
userInfo.Age = 33 + i;
//传入参数并执行异步委托,并设置回调函数
IAsyncResult result = myDelegate.BeginInvoke(userInfo, asyncCallback, null);
}
Console.WriteLine("Main thread working...");
Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
Console.WriteLine();
Console.ReadKey();
}
public static void Complete(IAsyncResult result)
{
UserInfo userInfoRes = null;
AsyncResult asyncResult = (AsyncResult)result;
//获取在其上调用异步调用的委托对象
MyDelegate myDelegate = (MyDelegate)asyncResult.AsyncDelegate;
//结束在其上调用的异步委托,并获取返回值
userInfoRes = myDelegate.EndInvoke(result);
Console.WriteLine("My name is " + userInfoRes.Name);
Console.WriteLine("I'm " + userInfoRes.Age + " years old this year");
Console.WriteLine("Thread ID is:" + userInfoRes.ThreadId);
}
}
public class ThreadDemoClass
{
public UserInfo Run(UserInfo userInfo)
{
userInfo.ThreadId = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("Child thread working...");
Console.WriteLine("Child thread ID is:" + userInfo.ThreadId);
Console.WriteLine();
return userInfo;
}
}
运行结果:
从上面可以看到主线程再执行了异步委托之后继续执行了下去,然后在回调函数里输出了信息,也就是说在调用了异步委托之后就不管了,把之后的结束委托和获取委托的返回值放到了回调函数中,因为回调函数是没有返回值的,但是回调函数可以有一个参数。上面说到的 BeginInvoke() 方法的最后两个参数,它的倒数第二个参数就是一个回调函数的委托,最后一个参数可以设置传入回调函数的参数。如下:
class Program
{
//定义一个委托类
private delegate UserInfo MyDelegate(UserInfo userInfo);
static List<UserInfo> userInfoList = new List<UserInfo>();
static void Main(string[] args)
{
ThreadDemoClass demoClass = new ThreadDemoClass();
UserInfo userInfo = null;
//创建一个委托并绑定方法
MyDelegate myDelegate = new MyDelegate(demoClass.Run);
//创建一个回调函数的委托
AsyncCallback asyncCallback = new AsyncCallback(Complete);
//回调函数的参数
string str = "I'm the parameter of the callback function!";
for (int i = 0; i < 3; i++)
{
userInfo = new UserInfo();
userInfo.Name = "Brambling" + i.ToString();
userInfo.Age = 33 + i;
//传入参数并执行异步委托,并设置回调函数
IAsyncResult result = myDelegate.BeginInvoke(userInfo, asyncCallback, str);
}
Console.WriteLine("Main thread working...");
Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
Console.WriteLine();
Console.ReadKey();
}
public static void Complete(IAsyncResult result)
{
UserInfo userInfoRes = null;
AsyncResult asyncResult = (AsyncResult)result;
//获取在其上调用异步调用的委托对象
MyDelegate myDelegate = (MyDelegate)asyncResult.AsyncDelegate;
//结束在其上调用的异步委托,并获取返回值
userInfoRes = myDelegate.EndInvoke(result);
Console.WriteLine("My name is " + userInfoRes.Name);
Console.WriteLine("I'm " + userInfoRes.Age + " years old this year");
Console.WriteLine("Thread ID is:" + userInfoRes.ThreadId);
//获取回调函数的参数
string str = result.AsyncState as string;
Console.WriteLine(str);
}
}
public class ThreadDemoClass
{
public UserInfo Run(UserInfo userInfo)
{
userInfo.ThreadId = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("Child thread working...");
Console.WriteLine("Child thread ID is:" + userInfo.ThreadId);
Console.WriteLine();
return userInfo;
}
}
运行结果:
回调函数的参数也是 object 类型的,我这里用的是一个 string 类型,但是它也可以是自定义类型的参数。
本篇博客到此结束,在写这篇博客的同时也让我个人对多线程编程加深了理解,多线程编程的知识点还有很多,后面再继续与大家分享。
参考:
1)http://www.cnblogs.com/leslies2/archive/2012/02/07/2310495.html#t1
2)https://www.cnblogs.com/Brambling/p/7144015.html
- 上一篇:多线程在C# (.NET) 中的应用
- 下一篇:C# 多 线 程。
相关推荐
- .NET Core 中推荐使用的10大优秀库,你用到过几个?
-
概述:Microsoft的.NETCore生态系统中的中间件已经发生了重大变化,包括无缝集成到应用程序管道中的内置和第三方组件,协调客户端和服务器之间的数据流。它通过身份验证、日志记录和路由等...
- 机器学习中英文对照表
-
10-1LossFunction0-1损失函数2Accept-RejectSamplingMethod接受-拒绝抽样法/接受-拒绝采样法3AccumulatedErrorBa...
- 反应式编程之Spring Web-Flux/Project Reactor
-
介绍反应式编程代表了我们对应用程序执行模型的看法的改变。在响应式应用程序中,执行不遵循一个请求由一个线程处理的线性模型,而是以事件驱动和非阻塞的方式处理多个请求。...
- Spider详解
-
简介Spider的功能主要使用于大型的应用系统测试,它能在很短的时间内帮助我们快速地对一个应用程序的内容、功能、系统的结构和分布情况进行了解。Control右键进行爬取数据使用spider功能。在Sp...
- WebUI 如何高效进行测试
-
1.选择合适的浏览器驱动ChromeDriver:对于大多数情况,推荐使用ChromeDriver,因为它与Chrome浏览器的兼容性好,并且性能较好。...
- 《成为Rust专家》五、单元测试 (2)
-
6.3测试框架Rust的单元测试不包括其他单元测试框架中可能找到的辅助函数、夹具、测试框架或参数化测试功能。对于这些功能,你需要自己编写代码或者尝试一些库。对于基本的参数化测试,parameteri...
- JUnit5学习之一:基本操作
-
欢迎访问我的GitHubhttps://github.com/zq2599/blog_demos内容:所有原创文章分类和汇总,及配套源码,涉及Java、Docker、Kubernetes、DevOPS...
- 511基于C# Thread类的大漠多线程模板游戏实战
-
如果你的游戏检测易语言,或者,客户反馈你的脚本被频繁报毒,加入黑名单,那么我们选择微软的C#来写一个大漠的多线程模板是最好的选择。...
- 如何深度理解mybatis?
-
深度自定义mybatis回顾mybatis的操作的核心步骤...
- .NET 6 多线程的几种打开方式
-
前言多线程无处不在,平常的开发过程中,应该算是最常用的基础技术之一了。以下通过Thread、ThreadPool、再到Task、Parallel、线程锁、线程取消等方面,一步步进行演示多线程的一些基础...
- C# 多 线 程。
-
一、基本概念1、进程...
- C#多线程
-
1.概念进程,线程,应用程序的定义网上有很多资料,但是有些抽象。通俗的来讲,进程就是一旦一个应用程序开始运行,那么这个应用程序就会存在一个属于这个应用程序的进程。线程就是进程中的基本执行单元,每个进...
- 多线程在C# (.NET) 中的应用
-
在实际项目应用中我们难免会用到多线程、多进程编程方式,C#中的多线程允许你在同一时间内执行多个线程,每个线程都可以独立地执行不同的任务或者处理不同的部分。这可以帮助提高应用程序的响应性和性能。通过这...
- 如何使?C#创建?个线程?
-
在C#中,可以通过多种方式创建和启动一个线程。以下是常用的方式及其具体实现。1.使用Thread类创建线程...
- 在C#中,如何创建并启动?个新的线程?请举例说明
-
在C#中,可以使用System.Threading.Thread类创建并启动一个新的线程。以下是创建和启动线程的方式以及示例代码:创建并启动线程的步骤...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)