Loading

C# :多线程入门

1. 理解进程、线程和任务

进程拥有资源,比如分配给进程的内存和线程。

线程一条一条地执行代码。默认情况下,每个进程只有一个线程。

现代操作系统使用了抢夺式多任务处理,从而模拟了任务的并行执行。

如果有少量的复杂工作要做,并且希望完全控制它们,那么可以使用Thread实例。如果有一个主线程和多个可以在后台执行的工作块,那么可以使用ThreadPool类添加一些委托实例,这些委托实例指向作为队列方法实现的工作做块,并且将它们自动分配给线程池中的线程。

2. 异步运行任务

创建一个.NET 6.0 控制台应用。

2.1 同步执行多个操作

在Program.cs中添加两个名称空间:

using System.Diagnostics;
using static System.Console;

添加几个静态方法:

static void OutputThreadInfo()
{
    Thread t = Thread.CurrentThread;
    WriteLine(
        "Thread ID:{0},Priority:{1},Background:{2},Name:{3}",
        t.ManagedThreadId,
        t.Priority,
        t.IsBackground,
        t.Name ?? "null"
        );
}
static void MethodA()
{
    WriteLine("启动 MethodA...");
    OutputThreadInfo();
    Thread.Sleep(3000);
    WriteLine("完成 MethodA");
}
static void MethodB()
{
    WriteLine("启动 MethodB...");
    OutputThreadInfo();
    Thread.Sleep(2000);
    WriteLine("完成 MethodB");
}
static void MethodC()
{
    WriteLine("启动 MethodC...");
    OutputThreadInfo();
    Thread.Sleep(1000);
    WriteLine("完成 MethodC");
}

在Program.cs的顶部添加语句,来调用该方法,输出关于线程的信息,定义并启动一个秒表,调用三个方法,然后输出经过的毫秒数:

OutputThreadInfo();
Stopwatch timer = Stopwatch.StartNew();

//同步执行多个操作
WriteLine("在一个thread中同步地运行方法");
MethodA();
MethodB();
MethodC();

执行结果:

image-20230421004634403

2.2 使用任务异步执行多个操作

Thread类从.NET第一个版本就存在了,但是直接使用比较麻烦。

.NET Framework 4.0 在2010年引入了Task类。Task类是线程的封装器,允许更容易的创建和管理线程。通过管理任务中封装的多个线程,可以实现代码的异步执行。

每个Task实例都有Status和CreationOptions属性,还有ContinueWith方法;该方法可以使用TaskContinuationOptions枚举进行自定义,也可以使用TaskFactory类进行管理。

启动任务

注释掉上面的同步执行代码,添加如下代码:

//使用任务异步执行多个操作
WriteLine("在多个thread中运行方法");
Task taskA = new(MethodA); taskA.Start();
Task taskB = Task.Factory.StartNew(MethodB);
Task taskC = Task.Run(MethodC);

执行结果如下:

image-20230421005633287

这三个方法都由线程池中分配的三个新的后台工作线程执行。

2.3 等待任务

方法列表:

方法 说明
t.Wait() 等待名为t的Task实例完成执行
Task.WaitAny(Task[]) 等待数组中的任何任务完成执行
Task.WaitAll(Task[]) 等待数组中的所有任务完成执行

对任务使用Wait方法

保留上面的代码,并在下面添加如下代码,注意添加在输出运行时间的那行代码之前:

//等待任务
Task[] tasks = { taskA, taskB, taskC };
Task.WaitAll(tasks);

执行结果:

image-20230421010313784

可以看到,三个方法同步执行,MethodC最先完成,因为它需要的时间最短。总体时间被运行时间最长的MethodA影响。

2.4 继续执行另一任务

如果一个任务依赖另一个任务的结果,那么可以使用ContinueWith,下面是代码示例:

注释掉上面的执行代码,添加如下代码,这些代码要添加在WriteLine($"{timer.ElapsedMilliseconds:#,##0}ms 流逝");之前:

static decimal CallWebSevice()
{
    WriteLine("开始调用web服务");
    OutputThreadInfo();
    Thread.Sleep((new Random()).Next(2000, 4000));
    WriteLine("完成调用web服务");
    return 89.99M;
}
static string CallStoreProcedure(decimal amount)
{
    WriteLine("开始调用存储程序");
    OutputThreadInfo();
    Thread.Sleep((new Random()).Next(2000, 4000));
    WriteLine("完成调用存储程序");
    return $"12个产品的价格超过{amount:C}";
}

//继续执行另一任务
WriteLine("将一个任务的结果作为一个inut传给另一个任务");
Task<string> task = Task.Factory
    .StartNew(CallWebSevice)    //返回Task<decimal>
    .ContinueWith(t =>          //返回Task<string>
    CallStoreProcedure(t.Result));
WriteLine($"Result: {task.Result}");

运行结果:

image-20230421011215449

posted @ 2023-04-21 01:14  sq800  阅读(21)  评论(0编辑  收藏  举报