• asp.net mvc 中使用async/await异步编程


    已经介绍过async/await异步编程,但是按照一般的异步编程的步骤,在asp.net mvc页面中使用异步编程好像会经常报一个错误,错误信息如下:

    现在无法开始异步操作。异步操作只能在异步处理程序或模块中开始,或在页生存期中的特定事件过程中开始。如果此异常在执行 Page 时发生,请确保 Page 标记为 <%@ Page Async="true" %>。此异常也可能表明试图调用“异步无效”方法,在 ASP.NET 请求处理内一般不支持这种方法。相反,该异步方法应该返回一个任务,而调用方应该等待该任务。

    在asp.net mvc中调用async/await异步编程编程的示例如下:

    在DownLoadTest类中的代码如下:

    Stopwatch watch = new Stopwatch();
    public DownLoadTest()
    {
        watch.Start();
    }
    public async Task<string> DownLoadStringTaskAsync(string url)
    {
        Debug.WriteLine(string.Format("异步程序获取{0}开始运行:{1,4:N0}ms", url, watch.Elapsed.TotalMilliseconds));
        WebClient wc = new WebClient();
        string str = await wc.DownloadStringTaskAsync(url);
        Debug.WriteLine(string.Format("异步程序获取{0}运行结束:{1,4:N0}ms", url, watch.Elapsed.TotalMilliseconds));
        return str;
    }

    在asp.net mvc的HomeController控制器中的代码如下:

    public ActionResult DownLoad()
    {
        DownLoadTest dwtest = new DownLoadTest();
        var task1 = dwtest.DownLoadStringTaskAsync("https://stackoverflow.com/");
        var task2 = dwtest.DownLoadStringTaskAsync("https://github.com/");
        Debug.WriteLine("task.Result等待结果打印");
        task1.Wait();
        task2.Wait();
        Debug.WriteLine("task1.Result.Length=" + task1.Result.Length);
        Debug.WriteLine("task2.Result.Length=" + task2.Result.Length);
        return Json(new { task1Status = task1.Status, task2Status = task2.Status }, JsonRequestBehavior.AllowGet);
    }

    运行上面的代码,访问/home/DownLoad会报错


    报错的代码就是上面的报错信息。查看Output窗口的调试信息,可以看到如下的输出:

    异步程序获取https://stackoverflow.com/开始运行:   1ms
    异步程序获取https://github.com/开始运行:  42ms
    task.Result等待结果打印

    使用async Task<ActionResult>

    我们使用异步的方法修改控制器中的方法public ActionResult DownLoad()

    public async Task<ActionResult> DownLoadAsync()
    {
        var task = await DownLoadWebsiteAsync();
        return Json(new { taskLength = task }, JsonRequestBehavior.AllowGet);
    }
    public async Task<string> DownLoadWebsiteAsync()
    {
        DownLoadTest dwtest = new DownLoadTest();
        return await Task.Run<string>(() =>
        {
            var task1 = dwtest.DownLoadStringTaskAsync("https://stackoverflow.com/");
            var task2 = dwtest.DownLoadStringTaskAsync("https://github.com/");
            Debug.WriteLine("task.Length等待结果打印");
            Debug.WriteLine("task1.Length=" + task1.Result.Length);
            Debug.WriteLine("task2.Length=" + task2.Result.Length);
            return "task1.Length=" + task1.Result.Length + ";" + "task2.Length=" + task2.Result.Length;
        });
    }

    我们添加了DownLoadAsync异步方法,前面添加了async关键字,并且返回类型ActionResult改为了Task<ActionResult>,下面是Output窗口信息如下:

    异步程序获取https://stackoverflow.com/开始运行:   7ms
    异步程序获取https://github.com/开始运行:  61ms
    task.Length等待结果打印
    异步程序获取https://stackoverflow.com/运行结束:1,863ms
    task1.Length=250885
    异步程序获取https://github.com/运行结束:1,958ms
    task2.Length=52687

    页面返回的信息如下:

    {"taskLength":"task1.Length=250885;task2.Length=52687"}
    可以看出,使用async Task<ActionResult>可以实现异步编程,利用await关键字,代码执行等待任务DownLoadWebsiteAsync的完成。

    使用并行执行多个异步操作

    上面的例子中,有两个下载的任务,我们自己写了一个异步方法DownLoadWebsiteAsync来整合实现两个网站的下载。其实我们可以利用Task.WhenAll()来实现,免去书写大量的代码。修改后的代码如下:

    public async Task<ActionResult> DownLoadAsync()
    {
        DownLoadTest dwtest = new DownLoadTest();
        var task1 = dwtest.DownLoadStringTaskAsync("https://stackoverflow.com/");
        var task2 = dwtest.DownLoadStringTaskAsync("https://github.com/");
        await Task.WhenAll(task1, task2);
        Debug.WriteLine("task.Length等待结果打印");
        Debug.WriteLine("task1.Length=" + task1.Result.Length);
        Debug.WriteLine("task2.Length=" + task2.Result.Length);
        return Json(new { task1Status = task1.Status, task2Status = task2.Status }, JsonRequestBehavior.AllowGet);
    }

    这里我们使用Task.WhenAll()创建一个任务,该任务将在数组中的所有 System.Threading.Tasks.Task`1 对象都完成时完成。运行代码,Output窗口返回信息如下:

    异步程序获取https://stackoverflow.com/开始运行:   1ms
    异步程序获取https://github.com/开始运行:  52ms
    异步程序获取https://github.com/运行结束:1,708ms
    异步程序获取https://stackoverflow.com/运行结束:1,874ms
    task.Length等待结果打印
    task1.Length=249971
    task2.Length=52678

    页面返回结果如下:

    {"task1Status":5,"task2Status":5}
    TaskStatus状态为5是RanToCompletion代表已成功完成执行的任务

    asp.net mvc中取消异步操作

    利用CancellationToken取消异步操作的已经在《.net中async/await异步编程》有介绍,这里说明一下asp.net mvc中利用AsyncTimeoutAttribute取消异步操作的示例。DownLoadTest类中使用下面的异步方法

    public async Task DoRunTaskAsync(string url, CancellationToken ct)
    {
        if (ct.IsCancellationRequested)
        {
            Debug.WriteLine(string.Format("取消{0}的运行 :{1,4:N0}ms", url, watch.Elapsed.TotalMilliseconds));
            return;
        }
        Debug.WriteLine(string.Format("下载{0}开始运行 :{1,4:N0}ms", url, watch.Elapsed.TotalMilliseconds));
        WebClient wc = new WebClient();
        await Task.Run(() =>
        {
            var task = wc.DownloadStringTaskAsync(url);
            while (!task.IsCompleted)
            {
                if (ct.IsCancellationRequested)
                {
                    Debug.WriteLine(string.Format("取消{0}的运行 :{1,4:N0}ms", url, watch.Elapsed.TotalMilliseconds));
                    return;
                }
            }
            if (task.IsCompleted)
                Debug.WriteLine(string.Format("下载{0}运行结束 :{1,4:N0}ms", url, watch.Elapsed.TotalMilliseconds));
        });
    }

    我们在home控制器中添加一个异步的方法DownLoadThreeWebsiteAsync,这里使用[AsyncTimeout(10000)]特性,设置超时时间为10秒钟。

    [AsyncTimeout(10000)]
    public async Task<ActionResult> DownLoadThreeWebsiteAsync(CancellationToken cancellationToken)
    {
        DownLoadTest dwtest = new DownLoadTest();
        var task1 = dwtest.DoRunTaskAsync("https://stackoverflow.com/", cancellationToken);
        var task2 = dwtest.DoRunTaskAsync("https://github.com/", cancellationToken);
        var task3 = dwtest.DoRunTaskAsync("https://www.google.com/", cancellationToken);
        await Task.WhenAll(task1, task2, task3);
        Debug.WriteLine("task.Result等待结果打印");
        Debug.WriteLine("task1.Status=" + task1.Status);
        Debug.WriteLine("task2.Status=" + task2.Status);
        Debug.WriteLine("task3.Status=" + task3.Status);
        return Json(new { task1Status = task1.Status, task2Status = task2.Status, task3Status = task3.Status }, JsonRequestBehavior.AllowGet);
    }

    执行代码,Output窗口返回的信息如下:

    下载https://stackoverflow.com/开始运行 :   1ms
    下载https://github.com/开始运行 :   4ms
    下载https://www.google.com/开始运行 :   5ms
    下载https://stackoverflow.com/运行结束 :1,170ms
    下载https://github.com/运行结束 :1,247ms
    取消https://www.google.com/的运行 :10,016ms
    task.Result等待结果打印
    task1.Status=RanToCompletion
    task2.Status=RanToCompletion
    task3.Status=RanToCompletion
    可以看到超时10秒钟,取消了下载https://www.google.com/的任务。

    页面报错如下:


    使用Task.ConfigureAwait(false)实现异步

    我们前面说的asp.net mvc中使用异步编程,必须要将返回类型ActionResult改为了Task<ActionResult>。有没有不改返回值Task<ActionResult>的方法呢,设置异步方法的Task为ConfigureAwait(false),就可以实现,我们在DownLoadTest类中添加以下的异步方法

    public async Task<string> DownLoadConfigureAwaitAsync(string url)
    {
        Debug.WriteLine(string.Format("异步程序获取{0}开始运行:{1,4:N0}ms", url, watch.Elapsed.TotalMilliseconds));
        WebClient wc = new WebClient();
        var str= await Task.Run(() =>
        {
            return wc.DownloadString(url);
        }).ConfigureAwait(false);
        Debug.WriteLine(string.Format("异步程序获取{0}运行结束:{1,4:N0}ms", url, watch.Elapsed.TotalMilliseconds));
        return str;
    }

    Home控制器中修改DownLoad

    public ActionResult DownLoad()
    {
        DownLoadTest dwtest = new DownLoadTest();
        var task1 = dwtest.DownLoadConfigureAwaitAsync("https://stackoverflow.com/");
        var task2 = dwtest.DownLoadConfigureAwaitAsync("https://github.com/");
        Debug.WriteLine("task.Result等待结果打印");
        Debug.WriteLine("task1.Result.Length=" + task1.Result.Length);
        Debug.WriteLine("task2.Result.Length=" + task2.Result.Length);
        return Json(new { task1Status = task1.Status, task2Status = task2.Status }, JsonRequestBehavior.AllowGet);
    }

    执行程序,Output输出窗口以下信息:

    异步程序获取https://stackoverflow.com/开始运行:   2ms
    异步程序获取https://github.com/开始运行:  12ms
    task.Result等待结果打印
    异步程序获取https://github.com/运行结束:1,831ms
    异步程序获取https://stackoverflow.com/运行结束:2,075ms
    task1.Result.Length=255319
    task2.Result.Length=52687
    页面的信息如下:
    {"task1Status":5,"task2Status":5}

    参考文档:https://docs.microsoft.com/en-us/aspnet/mvc/overview/performance/using-asynchronous-methods-in-aspnet-mvc-4

  • 相关阅读:
    从零开始学 ASP.NET Core 与 EntityFramework Core 介绍
    Spring Cloud Stream
    基于vue实现的三级联动下拉框
    中国十大瓜子品牌排行榜
    春秋战国2020
    spring boot 2 + shiro 实现权限管理
    轻松搭建CAS 5.x系列(1)-使用cas overlay搭建SSO SERVER服务端
    springboot+maven+thymeleaf配置实战demo
    Springboot系列:Springboot与Thymeleaf模板引擎整合基础教程
    idea使用maven install命令打包(springboot),jar运行时出现没有主清单属性
  • 原文地址:https://www.cnblogs.com/hueychan/p/10575912.html
Copyright © 2020-2023  润新知