ASP.NET Core 多线程 异步编程

@ASP.NET Core 多线程 异步编程
同步异步编程
同步编程是对于单线程来说的,就像我们编写的控制台程序,以main方法为入口,顺序执行我们编写的代码。
异步编程是对于多线程来说的,通过创建不同线程来实现多个任务的并行执行。

线程
.Net 1.0就发布了System.Threading,其中提供了许多类型(比如Thread、ThreadStart等)可以显示的创建线程。

主线程
每一个Windows进程都恰好包含一个用作程序入口点的主线程。进程的入口点创建的第一个线程被称为主线程。.Net执行程序(控制台、Windows Form、Wpf等)使用Main()方法作为程序入口点。当调用该方法时,主线程被创建。

工作者线程
由主线程创建的线程,可以称为工作者线程,用来去执行某项具体的任务。

前台线程
默认情况下,使用Thread.Start()方法创建的线程都是前台线程。前台线程能阻止应用程序的终结,只有所有的前台线程执行完毕,CLR才能关闭应用程序(即卸载承载的应用程序域)。前台线程也属于工作者线程。

后台线程
后台线程不会影响应用程序的终结,当所有前台线程执行完毕后,后台线程无论是否执行完毕,都会被终结。一般后台线程用来做些无关紧要的任务(比如邮箱每隔一段时间就去检查下邮件,天气应用每隔一段时间去更新天气)。后台线程也属于工作者线程。

//主线程入口
static void Main(string[] args)
{
Console.WriteLine(“主线程开始!”);

        //创建前台工作线程
        Thread t1 = new Thread(Task1);
        t1.Start();

        //创建后台工作线程
        Thread t2 = new Thread(new ParameterizedThreadStart(Task2));
        t2.IsBackground = true;//设置为后台线程
        t2.Start("传参");
    }

    private static void Task1()
    {
        Thread.Sleep(1000);//模拟耗时操作,睡眠1s
        Console.WriteLine("前台线程被调用!");
    }

    private static void Task2(object data)
    {
        Thread.Sleep(2000);//模拟耗时操作,睡眠2s
        Console.WriteLine("后台线程被调用!" + data);
    }

111.PNG
执行发现,【后台线程被调用】将不会显示。因为当所有的前台线程执行完毕后,应用程序就关闭了,不会等待所有的后台线程执行完毕,所以不会显示。

创建线程
static void Main(){
new Thread(Go).Start(); // .NET 1.0开始就有的
Task.Factory.StartNew(Go); // .NET 4.0 引入了 TPL
Task.Run(new Action(Go)); // .NET 4.5 新增了一个Run的方法
}

public static void Go(){
Console.WriteLine(“我是另一个线程”);
}
创建Thread的实例之后,需要手动调用它的Start方法将其启动。但是对于Task来说,StartNew和Run的同时,既会创建新的线程,并且会立即启动它。

线程池
线程池是为突然大量爆发的线程设计的,通过有限的几个固定线程为大量的操作服务,减少了创建和销毁线程所需的时间,从而提高效率,这也是线程池的主要好处。
ThreadPool适用于并发运行若干个任务且运行时间不长且互不干扰的场景。
还有一点需要注意,通过线程池创建的任务是后台任务。
线程的创建是比较占用资源的一件事情,.NET 为我们提供了线程池来帮助我们创建和管理线程。Task是默认会直接使用线程池,但是Thread不会。如果我们不使用Task,又想用线程池的话,可以使用ThreadPool类。

static void Main() {
Console.WriteLine(“我是主线程:Thread Id {0}”, Thread.CurrentThread.ManagedThreadId);
ThreadPool.QueueUserWorkItem(Go);

Console.ReadLine();

}

public static void Go(object data) {
Console.WriteLine(“我是另一个线程:Thread Id {0}”,Thread.CurrentThread.ManagedThreadId);
}
返回值
Task可以有返回值。

static void Main() {
// GetDayOfThisWeek 运行在另外一个线程中
var dayName = Task.Run(() => { return GetDayOfThisWeek(); });
Console.WriteLine(“今天是:{0}”,dayName.Result);
}
并行任务(Task)以及基于Task的异步编程(asynchronously)在.NET Framework中已经有,在.NET Core 平台下也有相同功能的实现,下面通过.NET Core WebAPI,介绍使用Task.result的同步编程以及使用await的异步编程模型。

Task.Result
.Net 4.0引入了System.Threading.Tasks,简化了我们进行异步编程的方式,而不用直接与线程和线程池打交道。
System.Threading.Tasks中的类型被称为任务并行库(TPL)。TPL使用CLR线程池(说明使用TPL创建的线程都是后台线程)自动将应用程序的工作动态分配到可用的CPU中。

Result方法可以返回Task执行后的结果,如下代码:

[HttpGet]
public static async Task GetJsonAsync(Uri uri)
{
using (var client = new HttpClient())
{
var jsonString = await client.GetStringAsync(uri);
return JObject.Parse(jsonString);
}
}

public class MyController : ApiController
{
public string Get()
{
var jsonTask = GetJsonAsync(…);
return jsonTask.Result.ToString();
}
}
但是如果在ASP.NET Core的webapi中使用result方法来获取task输出值,会造成当前API线程阻塞等待到task执行完成后再继续进行。可以通过下面代码来证明,get方法有一个线程,调用一个新线程执行task(taskcaller),在执行task时候由于需要等待task的执行结果,此时get方法的执行线程等待中,直到result结果输出,此线程继续完成方法。

[Route(“api/[controller]”)]
public class ValuesController : Controller
{
// GET: api/
[HttpGet(“get”)]
public async Task Get()
{
var info = string.Format(“api执行线程:{0}”, Thread.CurrentThread.ManagedThreadId);
var infoTask = TaskCaller().Result;//使用Result

        var infoTaskFinished = string.Format("api执行线程(task调用完成后):{0}", Thread.CurrentThread.ManagedThreadId);
        return string.Format("{0},{1},{2}", info, infoTask, infoTaskFinished);
    }

    private async Task<string> TaskCaller()
    {
        await Task.Delay(5000);
        return string.Format("task 执行线程:{0}", Thread.CurrentThread.ManagedThreadId);
    }
}

12.PNG
async & await
C# async关键字用来指定某个方法、Lambda表达式或匿名方法自动以异步的方式来调用。
async/await是用来进行异步调用的形式,内部其实还是采用线程池进行管理。
如果使用await,在调用 await taskcall() 时不会阻塞get主方法线程,主方法线程会被释放,新的线程执行完成task后继续执行await后的代码减少线程切换开销,而之前的线程则空闲了。

[Route(“api/[controller]”)]
public class ValuesController : Controller
{
// GET: api/
[HttpGet(“get”)]
public async Task Get()
{
var info = string.Format(“api执行线程:{0}”, Thread.CurrentThread.ManagedThreadId);
var infoTask = await TaskCaller();//使用await

        var infoTaskFinished = string.Format("api执行线程(task调用完成后):{0}", Thread.CurrentThread.ManagedThreadId);
        return string.Format("{0},{1},{2}", info, infoTask, infoTaskFinished);
    }

    private async Task<string> TaskCaller()
    {
        await Task.Delay(5000);
        return string.Format("task 执行线程:{0}", Thread.CurrentThread.ManagedThreadId);
    }
}

11.PNG

如上截图,一开始运行在线程10,后来跳到async方法中执行在线程8中,在没有使用await时,主线程并没有停下来,还是按照自己的路往下走,直到async使用了await方法,下面的代码也是交给了子线程。

至于为什么交给了子线程处理,有一篇文章说是await前后的代码被分成块,将await的task交给线程池,线程池执行完毕之后进行moveNext方法,继续执行await之后的代码。
可以看看这篇文章http://www.cnblogs.com/vd630/p/4596203.html
Task.result 与 await关键字 具有类似的功能可以获取到任务的返回值,但是本质上Task.result会让外层函数执行线程阻塞直到任务执行完成,而使用await关键字外层函数线程则不会阻塞,而是通过任务执行线程来执行await后的代码

默认创建的Thread是前台线程,创建的Task为后台线程。
ThreadPool创建的线程都是后台线程。
任务并行库(TPL)使用的是线程池技术。
调用async标记的方法,刚开始是同步执行的,只有当执行到await标记的方法中的异步任务时,才会挂起。
async和await优点
以ASP.NET MVC为例,如果不采用async的Action,那么它是在一个Woker线程中执行的。当我们访问一些web service,或者读文件的时候,这个Worker线程就会被阻塞。

用async/await可以在我们访问web service的时候把当前的worker线程放走,将它放回线程池,这样它就可以去处理其它的请求了。等到web service给我们返回结果了,会再到线程池中随机拿一个新的woker线程继续往下执行。也就是说我们减少了那一部分等待的时间,充份利用了线程。

*** 尽量不混合使用同步和异步代码,要异步就异步到底,不然可能会因为异常捕获、死锁等原因导致应用程序崩溃。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值