C# 异步编程 Thread、ThreadPool、Task

异步编程

从 .NET Framework 4 开始,使用多线程的推荐方法是使用任务并行库 (TPL) 和并行 LINQ (PLINQ)
任务并行库(TPL)说的是 System.Threading 和 System.Threading.Tasks 空间中的一组公共类型和 API。较为常见的就是Thread、ThreadPool、Task等
LINQ (PLINQ) 是语言集成查询 (LINQ) 模式的并行实现,可以理解为对LINQ的一些扩充方法,类似于“IEnumerable .AsParallel()”方法。

1、Thread和ThreadPoool

1.1、线程Thread

创建和控制线程,设置其优先级并获取其状态。
使用举例:

public static void ThreadProc()
{
    for (int i = 0; i < 10; i++)
    {
        Console.WriteLine("ThreadProc: {0}", i);
        Thread.Sleep(1);
    }
}

public static void MainThread()
{
    Console.WriteLine("Main thread Start.");
    Console.WriteLine("Creat a second thread: ThreadProc.");
    Thread t = new Thread(new ThreadStart(ThreadProc));
    Console.WriteLine("ThreadProc start.");
    t.Start();

    for (int i = 0; i < 4; i++)
    {
        Console.WriteLine("Main thread: Do some work.");
        Thread.Sleep(1);
    }

    Console.WriteLine("Main thread: Call Join(), to wait until ThreadProc ends.");
    t.Join();
    Console.WriteLine("Main thread: ThreadProc.Join has returned.  Press Enter to end program.");
    Console.ReadLine();
}

ThreadStart为系统自带无参委托类型。
Thread的构造函数有如下几种:

public Thread(ThreadStart start)...    
public Thread(ParameterizedThreadStart start)...
public Thread(ThreadStart start, int maxStackSize)...
public Thread(ParameterizedThreadStart start, int maxStackSize)...

ParameterizedThreadStart为带一个可控object参数的委托类型。后两种构造函数中的maxStackSize指定线程的最大堆栈大小。
Thread.Start()即启动线程
Thread.Join()即将线程加入到当前线程中使同步,或者说就是等线程结束后继续执行主线程。
Thread.Abort()是强制结束线程。
其他方法参考:Thread类

1.2、线程池ThreadPoool

先说说进程,进程是一种正在执行的程序,操作系统使用进程来分隔正在执行的应用程序。

可以把线程池比喻成公路,一个进程只有一条公路,一条公路(线程池)上可以有多个车道。即是说一个进程只能有一个线程池,而线程池中可以有多个线程,而具体可以有多少线程呢,是受到计算机内存等限制的。

C#中可以使用 ThreadPool.GetMaxThreads 和 ThreadPool.SetMaxThreads 方法来控制最大线程数。
使用示例:

public static void ThreadProc(object stateInfo)
{
    for (int i = 0; i < 5; i++)
    {
        Console.WriteLine("ThreadProc: {0},stateInfo: {1}", i, stateInfo);
        Thread.Sleep(1);
    }
}
public static void MainThreadPool()
{
    ThreadPool.QueueUserWorkItem(ThreadProc, "state");
    Console.WriteLine("Main thread does some work, then sleeps.");
    Thread.Sleep(1000);

    Console.WriteLine("Main thread exits.");
}

输出:

Main thread does some work, then sleeps.
ThreadProc: 0,stateInfo: state
ThreadProc: 1,stateInfo: state
ThreadProc: 2,stateInfo: state
ThreadProc: 3,stateInfo: state
ThreadProc: 4,stateInfo: state
Main thread exits.

ThreadPool为静态类,只能操作当前进程的线程池。
常用方法除了GetMaxThreads() 和 SetMaxThreads()外,还有上面使用示例中出现的:
QueueUserWorkItem(WaitCallback callBack, object state)
QueueUserWorkItem(WaitCallback callBack)
即将方法排入队列以便执行。 此方法在有线程池线程变得可用时执行。
参数WaitCallBack是一个内置委托(如下👇),需要传递参数时使用第一种方法。

delegate void WaitCallback(object? state);

2、Task

这是 .NET Framework 4 中引入的基于任务的异步模式的中心组件之一,也是一部编程中优先推荐使用的。

2.1、Task、Thread和ThreadPool

前面已经简单说了Thread和ThreadPool了,而Task和这两个又有什么关系呢。

首先这些都是多线程异步执行,Thread是创建新线程,ThreadPool是在已有的线程上处理事情。Task是借助ThreadPool(线程池)处理。线程池即为程序进程开辟的线程池子,可以将异步任务加入到已有的线程池中的线程上执行,线程池中的线程可以复用。理解为线程池是公路,线程是路上的车道,Task为运输车(任务),一个运输任务Task在一条车道上跑过后可以有别的Task去跑。而Thread则是每次都新挖一条车道跑完再把车道毁了。

2.2、Task使用

Task是更为灵活方便且被优先推荐的异步编程方式。这里说的Task包括Task(无返回值)和有返回值的 Task<TResult>。
先说前者,
用一个简单的例子说明使用方法:

static void MainConstruct()
{
    Action<object> action = (object obj) =>
    {
        Console.WriteLine("obj={0}, TaskId={1}, ThreadId={2}",
        obj, Task.CurrentId, 
        Thread.CurrentThread.ManagedThreadId);
    };

    Task t1 = new Task(action, "t1");
    Task t2 = Task.Factory.StartNew(action, "t2");
    t2.Wait();

    t1.Start();
    Console.WriteLine("t1 has been launched. (Main Thread={0})",Thread.CurrentThread.ManagedThreadId);
    t1.Wait();

    String taskData = "t3";
    Task t3 = Task.Run(() => {
        Console.WriteLine("obj={0}, TaskId={1}, ThreadId={2}",
                          taskData, Task.CurrentId,
                           Thread.CurrentThread.ManagedThreadId);
    });
    t3.Wait();

    Task t4 = new Task(action, "t4");
    t4.RunSynchronously();
    t4.Wait();
}

输出如下:

obj=t2, TaskId=1, ThreadId=3
t1 has been launched. (Main Thread=1)
obj=t1, TaskId=2, ThreadId=3
obj=t3, TaskId=3, ThreadId=3
obj=t4, TaskId=4, ThreadId=1

实例化任务

Task任务的实例化可以使用构造函数以及 Task.Factory.StartNew(Action<object?> action, object? state)和Task.Run(Action action)静态方法来完成。

不同的是:使用Task构造函数创建实例实际上是将任务的创建与执行分开,构造函数仅完成创建,任务的执行则需要Task实例通过Start()方法完成;而Task.Factory.StartNew()和Task.Run()在实例化任务时即让任务开始执行。

执行任务

前面说的Task.Factory.StartNew()和Task.Run()在实例化时即执行。除此之外:

Task.Sart()是对已实例化但还没有开始执行,且线程状态时可以执行的任务进行启动。意义很简单,但条件较为严格。实例化的任务,如果已经开始或取消或执行结束,则不能调用Start()方法。其次Start()方法只能在任务实例化的上下文中调用。

Task.RunSynchronously()方法也是启动,但不同的是,他将实例化任务强制已同步执行。

任务结束

如示例中的Task.Wait(),即在主线程(调用任务的线程)中等待任务结束。对于Task<TResult>,Task.Result即等待任务结束并获取任务TResult类型的返回值。

其次,Task.ContinueWith()是设置一个在任务结束后接下来要做的另一个任务,也是已异步方式运行的。

取消任务

使用Task执行异步时,取消没有像Thread.Abort()那样粗暴的方法。
需要CancellationToken,即一个消息令牌。它由CancellationTokenSource 通知。

public static void MainCancel()
{
    CancellationTokenSource source = new CancellationTokenSource();
    CancellationToken token = source.Token;
    token.Register(() => { Console.WriteLine("Canceled"); });
    Task task = Task.Run(() =>
    {
        Console.WriteLine("Task Running...");
        long v = 0;
        for (int i = 0; i < 100; i++)
        {
            v = i * i;
            Thread.Sleep(200);
            Console.WriteLine("v = {0}", v);
            if (token.IsCancellationRequested)
                break;
        }
        Console.WriteLine("Task End...");
    }, token);
    Thread.Sleep(1000);
    Console.WriteLine("Cancel Task");
    source.Cancel();
}

CancellationToken可以理解为一个全局的变量,它有CancellationTokenSource创建并管理。
但为什么不直接使用自定义的全局变量来控制线程结束呢?
一方面CancellationToken是很多支持取消的内置异步方法必要参数(或者说指定取消方法),其次CancellationToken是单次的,即取消后不可重置,其他自定义变量是要具有set()方法就有可能被其他线程修改。并且当多线程操作同一数据时是有死锁的可能。

CancellationTokenSource是线程安全的,最后CancellationTokenSource提供了较为灵活且丰富的其他方法,比如示例中的token.Register()注册取消回调方法等。

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个使用.NET Framework 3.5实现异步编程的示例: ```csharp using System; using System.Threading; class Program { static void Main(string[] args) { Console.WriteLine("Main thread started."); // 使用委托和回调函数实现异步编程 Console.WriteLine("Using delegate and callback to implement asynchronous programming."); MyDelegate myDelegate = new MyDelegate(DoSomething); myDelegate.BeginInvoke(5000, new AsyncCallback(Callback), null); // 使用线程池实现异步编程 Console.WriteLine("Using thread pool to implement asynchronous programming."); ThreadPool.QueueUserWorkItem(new WaitCallback(DoSomethingThreadPool), 3000); // 使用异步方法实现异步编程 Console.WriteLine("Using async method to implement asynchronous programming."); DoSomethingAsync(2000); Console.WriteLine("Main thread ended."); Console.ReadLine(); } // 定义委托和回调函数 public delegate void MyDelegate(int milliseconds); public static void DoSomething(int milliseconds) { Console.WriteLine("DoSomething started."); Thread.Sleep(milliseconds); Console.WriteLine("DoSomething completed."); } public static void Callback(IAsyncResult ar) { Console.WriteLine("Callback started."); MyDelegate myDelegate = (MyDelegate)ar.AsyncState; myDelegate.EndInvoke(ar); Console.WriteLine("Callback completed."); } // 使用线程池 public static void DoSomethingThreadPool(object state) { Console.WriteLine("DoSomethingThreadPool started."); int milliseconds = (int)state; Thread.Sleep(milliseconds); Console.WriteLine("DoSomethingThreadPool completed."); } // 使用异步方法 public static async void DoSomethingAsync(int milliseconds) { Console.WriteLine("DoSomethingAsync started."); await Task.Run(() => Thread.Sleep(milliseconds)); Console.WriteLine("DoSomethingAsync completed."); } } ``` 该示例使用了三种不同的方法来实现异步编程:使用委托和回调函数、使用线程池、使用异步方法。在每种方法中,都使用Thread.Sleep方法模拟耗时操作。注意,在使用异步方法时,需要使用async和await关键字来标记异步方法和等待异步操作完成。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值