VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > Python基础教程 >
  • C#教程之异步编程

前段时间, 拿到一个框架, 之前也没怎么看, 只记得里面使用了蛮多的异步. 

public async Task<ActionResult> Login(LoginModel model, string returnUrl)

之前的项目中, 没有使用过异步的. 可能有人会把多线程和异步混为一谈, 其实还是不一样的东西.

那么, 今天就先来学习下异步, 以备使用之需. 这里只介绍新的方式了, 至于之前老的方式, 有些复杂, 没有新方式直观, 简洁.

一. 知识点

异步方法:提供了一种简便方式完成可能需要长时间运行的工作,而不必阻止调用方的线程。 异步方法的调用方可以继续工作,而不必等待异步方法完成。

await:运算符应用于一个异步方法的任务挂起方法的执行,直到等待任务完成。 任务表示正在进行的工作。 await 表达式不阻止它在其上执行的线程

async: async 修饰符指示方法、它进行修改 lambda 表达式或 匿名方法 是异步的

Task类:它表示一个任务,在.net4.5版本开始被支持, 它隶属于 System.Threading.Tasks命名空间下;通过Task类可以方便的开启一个新的线程。

 

二、小Demo

1.mvc文件下载

 在mvc源码解析的时候, 不知道我有没有提过, mvc在默认模式下, 是使用的异步方式来完成工作的. 那先来看一个mvc里面的应用吧

复制代码
public class HomeController : Controller
{
    public async Task<FileResult> DownLoad(string name)
    {
        var path = Server.MapPath("~/FileRes/") + name;
        using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 4096, FileOptions.Asynchronous))
        {
            byte[] data = new byte[fs.Length];
            await fs.ReadAsync(data, 0, data.Length);
            return File(data, "application/octet-stream", name);
        }
    }
}
复制代码

这里读取文件, 是以异步的方式来读取的, 当读取完成之后, 就会将fs关掉, 所以在返回文件的时候, 并不能使用 return File(fs,"application/octet-stream",name)的方式去返回了. 

 

2. 同步方法调用异步方法, 以及异步方法调用异步方法

复制代码
static void Main(string[] args)
{
    Console.WriteLine("Main Start : " + Thread.CurrentThread.ManagedThreadId);
    Step1();
    Step2();
    Console.WriteLine("Main End : " + Thread.CurrentThread.ManagedThreadId);
    Console.ReadKey();
}

static async void Step1()
{
    Console.WriteLine("Step1 start : " + Thread.CurrentThread.ManagedThreadId);
    try
    {
        await Task.Run(() => {
            Console.WriteLine("Step1.1 Current sleeping ThreadID : " + Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(3000);
        });

        await Task.Run(() =>
        {
            Console.WriteLine("Step1.2 Current sleeping ThreadID : " + Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(3000);
            Console.WriteLine("ThreadTest.Test Runing : " + Thread.CurrentThread.ManagedThreadId);
        });
    }
    catch (Exception ex)
    {
        Console.WriteLine("ThreadTest : " + ex.Message);
    }
    await Step3();
    Console.WriteLine("Step1 end : " +Thread.CurrentThread.ManagedThreadId);
}

static void Step2()
{
    Console.WriteLine("Step2 start : " + Thread.CurrentThread.ManagedThreadId);
    Console.WriteLine("Step2 Current ThreadID : " + Thread.CurrentThread.ManagedThreadId);
    Console.WriteLine("Step2 end : " + Thread.CurrentThread.ManagedThreadId);
}

static async Task Step3()
{
    Console.WriteLine("Step3 start : " + Thread.CurrentThread.ManagedThreadId);
    await Task.Run(() =>
    {
        Console.WriteLine("Step3 Current sleeping ThreadID : " + Thread.CurrentThread.ManagedThreadId);
        Thread.Sleep(5000);
    });
    Console.WriteLine("Step3 end : " + Thread.CurrentThread.ManagedThreadId);
}
复制代码

Main方法调用方法Step2是同步方法调用同步方法, 调用Step1是同步方法调用异步方法.

Step1方法调用Step3方法, 是异步方法调用异步方法.

运行结果:

这里我运行了好几次, 从上面的结果, 可以看出以下几点:

1. 主线程全程没有被阻塞, 一直执行到结束.

  如果我在Step2方法中, 加上一句 : Thread.Sleep(10), 那么主线程就会被阻塞在这里, 等待10s中的时间. 啥事都不用干的感觉, 真好.

2. 主线程在Step1方法中, 碰到await等待的时候, 完全没理会, 直接回到Main方法中, 去执行Step2方法了.

  这里就是同步方法调用异步方法时要注意的地方, 因为同步方法不会理await的, 并不会乖乖的在这里等await后面的方法执行结束, 还有一点, await下面的方法, 主程序也不会去管了.

3. 注意到这里的Step1.1和Step1.2的执行线程, 有时候相同, 有时候不同. 这是为什么呢?

  这些线程应该都是从线程池中取的, 在异步方法中, 如果当前正在使用的线程已经不需要用了, 会把他还给线程池, 由线程池再分配出去, 给别的线程用. 当休息时间到了之后, 会再向线程池请求线程来完成下面的工作. 有点像打车, 到了一个地方之后, 下车了, 就把出租车这个资源释放掉了, 等一个小时之后, 要回去了, 再打一个车, 这时候的出租车, 可能是之前打的那辆, 当然也可能不是. 这样做的一个好处, 就是能增加吞吐量, 提升并发处理能力. 这里出租车如果一直在这里等你的话, 你是不需要付钱的, 赚的少了, 出租车可不怎么愿意了, 哈哈. 

 4. Step1调用Step3的时候, 很明显的异步方法调用异步方法, 那么他会不会像之前的同步方法调用异步方法那样呢?

  从上面的图中, 就能很清晰的看到, 并不是一样的. 效果上, 像同步方法调用同步方法那样, 等在这里, 一直等你到天荒地老. 

猜想:

如果从计算机原理上来分析这里的异步挂起切换线程, 我猜想过程应该是这样子的:

  当程序运行到sleep之前的时候, 会将数据什么的存入到存储器和寄存器, 碰到sleep的时候, 就会释放线程, 开启定时器, 定时器到点触发, 通知线程池, 我这边需要一条线程来继续处理, 此时线程池收到消息, 会去池中查看哪些线程可用, 如果都没有, 会先等待一会, 可能是0.5s吧, 如果还没有可用的线程释放出来, 就会创建一条线程, 来满足要求, 完成工作.

 


相关教程