zoukankan      html  css  js  c++  java
  • ASP.NET Web API基础(04)---异步编程和跨域请求

    4.1 异步编程

    4.1.1 线程回顾

    说到异步编程,离不开多线程。在前面的课程中我们学习过多线程。回顾一下我们之前的例子。

        public static void DoWork()

        {

            Thread.Sleep(1000);

            Console.WriteLine("线程中执行任务……");

    }

    static void Main(string[] args)

        {

            Thread thread = new Thread(DoWork);

            thread.Start();

            Console.WriteLine("主程序继续执行!");

     

            Console.ReadLine();

    }

    允许结果如下。

     

    多线程的意义在于一个应用程序中,有多个执行部分可以同时执行;对于比较耗时的操作(例如io,数据库操作),或者等待响应(如WCF通信)的操作,可以单独开启后台线程来执行,这样主线程就不会阻塞,可以继续往下执行;等到后台线程执行完毕,再通知主线程,然后做出对应操作!

    试想一下,如果有大量的任务需要处理,例如网站后台对于HTTP请求的处理,那是不是要对每一个请求创建一个后台线程呢?显然不合适,这会占用大量内存,而且频繁地创建的过程也会严重影响速度,那怎么办呢?线程池就是为了解决这一问题,把创建的线程存起来,形成一个线程池(里面有多个线程),当要处理任务时,若线程池中有空闲线程(前一个任务执行完成后,线程不会被回收,会被设置为空闲状态),则直接调用线程池中的线程执行。

    下面的代码就是从线程池中创建线程并执行。

        for (int i = 0; i < 10; i++)

        {

            ThreadPool.QueueUserWorkItem(m =>

            {

                Console.WriteLine("线程ID:"+

    Thread.CurrentThread.ManagedThreadId.ToString());

            });

        }

        Console.Read();

    运行结果如下。

     

    可以看到,虽然执行了10次,但并没有创建10个线程。

    4.1.2 Task 和 Task<TResult>

    Task是.NET4.0加入的,跟线程池ThreadPool的功能类似,用Task开启新任务时,会从线程池中调用线程,而Thread每次实例化都会创建一个新的线程。

    Console.WriteLine("主线程启动");

    Task task = Task.Run(() => {

        Thread.Sleep(1500);

        Console.WriteLine("task启动");

    });

    task.Wait();

    Console.WriteLine("主线程结束");

    运行结果如下。

     

    开启新任务的方法:Task.Run()或者Task.Factory.StartNew(),开启的是后台线程。

    要在主线程中等待后台线程执行完毕,可以使用Wait方法(会以同步的方式来执行)。不用Wait则会以异步的方式来执行。

    Task<TResult>就是有返回值的Task,TResult就是返回值类型。

        Console.WriteLine("主线程开始");

        //返回值类型为string

        Task<string> task = Task<string>.Run(() => {

            Thread.Sleep(2000);

            return "任务返回的结果";

        });

        //会等到task执行完毕才会输出;

        Console.WriteLine(task.Result);

        Console.WriteLine("主线程结束");

    运行结果如下。

     

    通过task.Result可以取到返回值,若取值的时候,后台线程还没执行完,则会等待其执行完毕。

    4.1.3 async/await

    C# 5.0中引入了async 和 await。这两个关键字可以让你更方便的写出异步代码。用法如下。

    static Task<string> GetString()

        {

            //Console.WriteLine("GetString方法开始执行")

            return Task<string>.Run(() =>

            {

                Thread.Sleep(2000);

                return "GetString的返回值";

            });

        }

     

        static async Task<int> GetStrLengthAsync()

        {

            Console.WriteLine("GetStrLengthAsync方法开始执行");

            //此处返回的<string>中的字符串类型,而不是Task<string>

            string str = await GetString();

            Console.WriteLine("GetStrLengthAsync方法执行结束");

            return str.Length;

    }

    static void Main(string[] args)

        {

            Console.WriteLine("-------主线程启动-------");

            Task<int> task = GetStrLengthAsync();

            Console.WriteLine("主线程继续执行");

            Console.WriteLine("Task返回的值" + task.Result);

            Console.WriteLine("-------主线程结束-------");

            Console.Read();

    }

    运行结果如下。

     

    可以看出来,main函数调用GetStrLengthAsync方法后,在await之前,都是同步执行的,直到遇到await关键字,main函数才返回继续执行。

    上面提到task.wait可以让主线程等待后台线程执行完毕,await和wait类似,同样是等待,等待Task<string>.Run()开始的后台线程执行完毕,不同的是await不会阻塞主线程,只会让GetStrLengthAsync方法暂停执行。

    async用来修饰方法,表明这个方法是异步的,声明的方法的返回类型必须为:void,Task或Task<TResult>。

    await必须用来修饰Task或Task<TResult>,而且只能出现在已经用async关键字修饰的异步方法中。通常情况下,async/await成对出现才有意义。

    所有Task<TResult>的返回值都是直接用task.result获取,这样如果后台任务没有执行完毕的话,主线程会等待其执行完毕,这样的话是不是就和同步一样了?看上去一样,但其实await的时候并不会造成线程的阻塞。我们看下面的示例。

        static void Main(string[] args)

        {

            Console.WriteLine("主线程开始");

            Task<string> task = Task<string>.Run(() => {

                Thread.Sleep(2000);

                return "任务返回的结果";

            });

            //会等到任务执行完之后执行

            task.GetAwaiter().OnCompleted(() =>

            {

                Console.WriteLine(task.Result);

            });

            Console.WriteLine("主线程结束");

            Console.Read();

        }

    OnCompleted中的代码会在任务执行完成之后执行,运行结果如下。

     

    4.1.4 在ASP.NET Web API中使用async/await

    当在数据库中执行查询时,异步查询可避免阻止线程。异步操作还可以增加 Web 应用程序的吞吐量,可以在数据库操作完成时释放线程去处理其他请求。

    1. 异步查询

    public async Task<IHttpActionResult> GetStudentAsync()

    {

        Student student = null;

        using (var context = new SchoolDBEntities())

        {

             //注意await和FirstOrDefaultAsync

            student = await (context.Students.Where(s => s.StudentID == 1).FirstOrDefaultAsync<Student>());

        }

        return Json(student);

    }

    1. 异步保存

    public async Task<IHttpActionResult> Put(Student stu)

    {

        using (var context = new SchoolDBEntities())

        {

            context.Entry(stu).State = EntityState.Modified;

            int x = await (context.SaveChangesAsync());

            return Ok("保存完成");

        }

    }

    在System.Data.Entit.QueryableExtensions 扩展类中,提供了一系列异步方法,如:FirstOrDefaultAsync()、ToListAsync()、ToArrayAsync()、SingleAsync()等。另外还有DbContext对象的SaveChangesAsync()方法。

    4.2 跨域请求

    4.2.1 什么是跨域

    跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实施的安全限制。

    同源策略限制了一下行为:

    l  Cookie、LocalStorage 和 IndexDB 无法读取

    l  DOM 和 JS 对象无法获取

    l  Ajax请求发送不出去

    2. 常见的跨域场景

    所谓的同源是指,域名、协议、端口均为相同。

    URL

    说明

    是否允许

    http://www.aoxiang.cn/index.html

    http://www. aoxiang.cn/server.php

    非跨域

    允许

    http://www. aoxiang.cn/index.html

    http://www.neal.cn/server.php

    跨域,主域不同

    不允许

    http://abc. aoxiang.cn/index.html

    http://def. aoxiang.cn/server.php

    跨域,子域名不同

    不允许

    http://www. aoxiang.cn:8080/index.html

    http://www. aoxiang.cn/server.php

    跨域,端口不同

    不允许

    https://www. aoxiang.cn/index.html

    http://www. aoxiang.cn/server.php

    跨域,协议不同

    不允许

    当发生跨域时,经常会引发如下形式的异常。

     

    4.2.2 跨域问题解决方案

    1. Mvc和WebApi通用的模式

    该模式是MVC和WebApi通用的一种处理模式,简单便捷,不需要额外添加多余的程序集,只需要在WebConfig中进行配置一下即可。

    同时缺点也比较明显,那就是只能全局配置,配置完后,所有的控制器下的方法都支持跨域了。

    代码配置如下,在 <system.webServer></system.webServer>节点的最顶部添加如下代码:

    <system.webServer>
        <!--允许跨域请求的配置  WebApi和MVC通用-->
        <httpProtocol>
          <customHeaders>
            <add name="Access-Control-Allow-Origin" value="*" />
            <add name="Access-Control-Allow-Headers" value="Access-Control-Allow-Origin, AppKey, Authorization" />
            <add name="Access-Control-Allow-Methods" value="GET, POST, OPTIONS" />
            <add name="Access-Control-Request-Methods" value="GET, POST, OPTIONS" />
          </customHeaders>
        </httpProtocol>
        <!--允许跨域请求的配置  WebApi和MVC通用   至此结束-->
        <!—略……-->
      </system.webServer>

    其中,

    l  Access-Control-Allow-Origin :代表允许请求的地址,如:" http://localhost:2131, http://localhost:2133" 多个地址之间用逗号隔开,*  代表运行所有

    l  Access-Control-Allow-headers: 代表允许的表头

    l  Access-Control-Allow-method: 代表允许请求的方法。如:"GET,PUT,POST,DELETE"

    2. WebApi特有的处理方式

    首先通过Nuget添加【Microsoft.AspNet.WebApi.Cors】程序集。该程序集中的核心方法:

    EnableCorsAttribute(string origins, string headers, string methods)

    其参数分别表示:

    origins代表允许请求的地址:" http://localhost:2131, http://localhost:2133" 多个地址之间用逗号隔开。

    headers代表允许的表头。

    method代表允许请求方法:"GET,PUT,POST,DELETE"

    * 代表允许所有。

    该模式和上述通用的模式相比较, 最大的好处就是比较灵活,既可以作用于全局,也可以特性的形式作用于Controller,或者直接作用于Action。

    1. 作用于全局

    在WebApiConfig类中的Register方法中添加:config.EnableCors(new EnableCorsAttribute("*", "*", "*"));

    2. 作用于Controller

    (1). 在WebApiConfig类中的Register方法中添加:config.EnableCors();

    (2). 在FifthController控制器上添加特性:[EnableCors("*", "*", "*")]

    3. 作用于Action

    (1). 在WebApiConfig类中的Register方法中添加:config.EnableCors();

    (2). 在GetUserName2方法上添加特性:[EnableCors("*", "*", "*")]

    部分代码如下所示:

     

  • 相关阅读:
    linux-shell编程-1-简介
    linux-tail
    linux-grep
    linux-sort
    linux-sed
    linux-awk
    函数调用
    选择结构和循环结构
    列表字典集合常用函数
    datetime模块
  • 原文地址:https://www.cnblogs.com/mrfang/p/13911781.html
Copyright © 2011-2022 走看看