【问题标题】:Can ASP.NET MVC 5 use Async methods?ASP.NET MVC 5 可以使用异步方法吗?
【发布时间】:2022-12-09 16:47:13
【问题描述】:

带有 .NET Framework v4.5.2 的 ASP.NET MVC 5

我有一个已分配给我的大型现有项目。它的某些部分需要很长时间,所以我想看看如何将这些“对我来说是新的”异步调用放入代码中。

但是,在查看示例之后,我不知道我所拥有的是否可以做到这一点。

在我的控制器上采用这种典型方法:

public ActionResult DealerSelect()
{
    var model = RetailActivityModelData.GetCurrentDealers(null, ViewBag.Library);
    return View(model);
}

我真的不明白如何把它变成异步调用,除非我做了这样的事情:

public async Task<ActionResult> DealerSelect()
{
    var model = await RetailActivityModelData.GetCurrentDealers(null, ViewBag.Library);
    return View(model);
}

但是编译器抱怨:

异步方法的返回类型必须是 void、Task、Task、类似任务的类型、IAsyncEnumerable 或 IAsyncEnumerator

我不确定如何在不破坏 View 模型的情况下更改返回类型。

因此,我进入 GetCurrentDealers 方法进行基本的 BackgroundWorker 调用:

public static DealerModel GetCurrentDealers(DealerModel model, string library)
{
    m_library = library;
    if (model == null)
    {
        model = new AdminCurrentDealerModel();
    }
    using (var bg = new BackgroundWorker())
    {
        var mre = new ManualResetEvent(false);
        bg.DoWork += delegate (object s, DoWorkEventArgs e)
        {
            // fill 3 DataTables with calls to database
        };
        bg.ProgressChanged += delegate (object s, ProgressChangedEventArgs e)
        {
            // add data from DataTables to model
        };
        bg.RunWorkerCompleted += delegate (object s, RunWorkerCompletedEventArgs e)
        {
            mre.Set();
        };
        bg.RunWorkerAsync();
        if (bg.IsBusy)
        {
            mre.WaitOne();
        }
    }
    return model;
}

它编译得很好,但一旦调用 bg.RunWorkerAsync() 就会崩溃:

System.InvalidOperationException:'此时无法启动异步操作。异步操作只能在异步处理程序或模块中或在页面生命周期中的某些事件期间启动。如果在执行页面时发生此异常,请确保页面标记为 <%@ Page Async="true" %>。此异常还可能指示尝试调用“async void”方法,这在 ASP.NET 请求处理中通常不受支持。相反,异步方法应该返回一个任务,调用者应该等待它。

这是一个 ASP.NET MVC Razor 页面,因此它实际上没有 &lt;%@ Page Async="true" %&gt; 部分。

我什至可以在这个项目上进行增量异步开发,还是需要将整个项目转换为使用任务的项目?

【问题讨论】:

    标签: asp.net-mvc asynchronous


    【解决方案1】:

    带有 .NET Framework v4.5.2 的 ASP.NET MVC 5

    请看我的文章Introduction to Async on ASP.NET。特别是,确保您正确设置了httpRuntime.targetFramework以避免服务器端“怪癖模式”。

    进行基本的 BackgroundWorker 调用

    永远不要在 ASP.NET 上使用 BackgroundWorker。而且几乎从不使用Task.Run(或StartNew)。你不能用这种方式“做一些异步的事情”。

    您遇到的异常是“安全网”异常。

    我真的不明白如何把它变成异步调用

    不要从控制器开始。这实际上是对面的最简单的方法。相反,从执行任何 I/O 的最低级别代码开始。例如,数据库查询或调用外部 API。一次一个,做那些异步(使用 await 调用异步 API),然后允许 async/await 用法从那里扩展。最终,您的控制器方法将变为 async 作为最后的这个过程的步骤。

    我什至可以在这个项目上进行增量异步开发,还是需要将整个项目转换为使用 Tasks 的项目?

    当您转换为 async 时,您可能会发现(暂时)拥有两份代码很有用:一份异步(由已转换的代码使用)和一份同步(由未转换的代码使用)。如果您正在执行不会被其他功能工作打断的异步转换,这很好。如果您需要对异步和同步代码的长期支持,那么您可以使用boolean argument hack described in my Brownfield Async article(或者如果您想要更现代/复杂的解决方案,请使用我博客中所述的generic code generation)。

    【讨论】:

    • 我是一名承包商,有 6 个月的时间来解决错误并尝试使他们的网站更快。我无权重建他们的整个网站。使用 WinForms 这很容易。听起来没有办法将 public ActionResult DealerSelect() 调整为异步方法。
    • 1)“异步”并不一定意味着“更快”; async 允许您同时处理更多请求,但每个请求花费的时间相同。 2) 这对于 WinForms 来说并不容易。如果您正在寻找快速的性能胜利,我的经验是修复低效的 SQL 语句通常是最划算的方法。
    • 有 3 个数据库调用收集有关模型的信息。 1) 经销商,2) 联系人,以及 3) 产品。我可以调用每一个,在ProgressChangedEventArgs 中传递表格,在处理结果时在线程中进行下一次调用。我经常这样做。公司仍然没有弄清楚如何让我访问 IBM AS400 数据库,已经一个月了。
    【解决方案2】:

    您的方法 GetCurrentDealers() 不是异步的,因此您不能使用 await 调用它。

    编辑

    控制器

    public async Task<IActionResult> DealerSelect()
    {
        var model = await RetailActivityModelData.GetCurrentDealersAsync(null, ViewBag.Library);
        return View(model);
    }
    

    GetCurrentDealersAsync()

    public static async Task<DealerModel> GetCurrentDealersAsync(DealerModel model, string library)
    {
        m_library = library;
        if (model == null)
        {
            model = new AdminCurrentDealerModel();
        }
        using (var bg = new BackgroundWorker())
        {
            var mre = new ManualResetEvent(false);
            bg.DoWork += delegate (object s, DoWorkEventArgs e)
            {
                // fill 3 DataTables with calls to database
            };
            bg.ProgressChanged += delegate (object s, ProgressChangedEventArgs e)
            {
                // add data from DataTables to model
            };
            bg.RunWorkerCompleted += delegate (object s, RunWorkerCompletedEventArgs e)
            {
                mre.Set();
            };
            bg.RunWorkerAsync();
            if (bg.IsBusy)
            {
                mre.WaitOne();
            }
        }
        return model;
    }
    

    【讨论】:

    • 我明白那个。我正在尝试找出如何完成它。如果我将 GetCurrentDealers() 写成异步,那么我就不能从 public ActionResult DealerSelect() 调用它
    • 你可以让控制器异步:public async Task&lt;IActionResult&gt; GetCurrentDealers()
    • 这听起来很有希望,但我将如何修改返回类型return View(model);?如果我修改它以返回Task&lt;IActionResult&gt;,Razor ASP.NET 页面不知道如何处理结果。
    • 这很奇怪。通常,如果端点是异步的,您可以一如既往地返回视图。我将编辑答案并添加我将如何做的代码。
    • 你能给我看看你的剃刀页面吗?
    猜你喜欢
    • 1970-01-01
    • 2018-05-20
    • 2014-05-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多