【问题标题】:Async functions in C# deadlock when called from javascript aspx page via PageMethods通过 PageMethods 从 javascript aspx 页面调用 C# 死锁中的异步函数
【发布时间】:2018-05-01 19:30:13
【问题描述】:

问题总结:
我正在尝试使用 PageMethods 从 HTML 页面调用 C# 函数。问题是我正在调用的 C# 函数被标记为异步并且将等待其他函数的完成。当 PageMethods 调用嵌套的异步 C# 函数时,C# 代码似乎死锁了。
我已经给出了一个示例 ASP.NET 页面,其后面带有 C# 编码来说明我正在尝试使用的习语。

WebForm1.aspx 示例

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs" Inherits="WebApplication3.WebForm1" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head><title></title></head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server" EnablePageMethods="true"/>
        <div>
            <input type="button" value="Show Function timing" onclick="GetTiming()"/>
        </div>
    </form>
</body>

<script type="text/javascript">
    function GetTiming() {
        console.log("GetTiming function started.");
        PageMethods.GetFunctionTiming(
            function (response, userContext, methodName) { window.alert(response.Result); }
        );
        console.log("GetTiming function ended."); // This line gets hit!
    }
</script>

</html>

WebForm1.aspx.cs 示例

using System;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Web.Services;
using System.Web.UI;
namespace WebApplication3
{
    public partial class WebForm1 : Page
    {
        protected void Page_Load(object sender, EventArgs e) { }
        [WebMethod]
        public static async Task<string> GetFunctionTiming()
        {
            string returnString = "Start time: " + DateTime.Now.ToString();
            Debug.WriteLine("Calling to business logic.");

            await Task.Delay(1000); // This seems to deadlock
            // Task.Delay(1000).Wait(); // This idiom would work if uncommented.

            Debug.WriteLine("Business logic completed."); // This line doesn't get hit if we await the Task!
            return returnString + "\nEnd time: "+ DateTime.Now.ToString();
        }
    }
}

问题:
我绝对需要能够从我的网页 UI 调用异步代码。我想使用 async/await 功能来做到这一点,但我无法弄清楚如何去做。我目前正在通过使用 Task.Wait()Task.Result 而不是 async/await 来解决这个缺陷,但这显然不是推荐的长期解决方案。
如何等待服务器端异步函数在 PageMethods 调用的上下文中???
我真的非常想了解这里发生了什么,以及为什么从控制台调用异步方法时它不会发生应用程序。

【问题讨论】:

  • 出于好奇,为什么是页面方法而不是 jquery 和 ajax?
  • 为什么绝对需要使用异步代码?是什么迫使你这样做?
  • await deadlocking 和 .Wait() not deadlocking 没有任何意义。 Task.Delay 并没有真正发生这种情况。是吗?
  • 我最初使用 PageMethods 是因为它与 IIS 和 .net 堆栈紧密集成,因此在我工作的 C# 商店中使用它似乎是自然的选择。我开始为这个决定感到后悔,在这一点上,我坚持着固执和好奇。 ;-)
  • 我的真实代码使用异步HTTP请求。我只使用 Task.Delay 来说明异步/等待编程习惯的奇怪行为。

标签: c# asp.net asynchronous async-await pagemethods


【解决方案1】:

我想出了一种方法,它允许 PageMethods 在 ASP.NET 上调用嵌套的异步函数而不会死锁。我的方法涉及

  1. 使用 ConfigureAwait(false) 这样我们就不会强制异步函数尝试返回原始捕获的上下文(Web UI 线程将锁定该上下文);和
  2. 将“顶级”异步函数强制到线程池中,而不是让它在 ASP.NET 的 UI 上下文中运行。

这两种方法中的每一种通常都在论坛和博客上被推荐反对,所以我确信同时使用这两种方法构成了一种反模式。但是,它确实允许有用的 shim 通过使用 PageMethods 从 ASP.NET 网页调用异步函数。
下面给出了示例 C# 代码。

WebForm1.aspx.cs 示例

using System;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Web.Services;
using System.Web.UI;

namespace WebApplication3
{
    public partial class WebForm1 : Page
    {

        protected void Page_Load(object sender, EventArgs e) { }

        [WebMethod]
        public static async Task<string> GetFunctionTiming()
        {
            Debug.WriteLine("Shim function called.");
            string returnString = "Start time: " + DateTime.Now.ToString();

            // Here's the idiomatic shim that allows async calls from PageMethods
            string myParameter = "\nEnd time: "; // Some parameter we're going to pass to the business logic
            Task<string> myTask = Task.Run( () => BusinessLogicAsync(myParameter) ); // Avoid a deadlock problem by forcing the task onto the threadpool
            string myResult = await myTask.ConfigureAwait(false); // Force the continuation onto the current (ASP.NET) context

            Debug.WriteLine("Shim function completed.  Returning result "+myResult+" to PageMethods call on web site...");
            return returnString + myResult;
        }

        // This takes the place of some complex business logic that may nest deeper async calls
        private static async Task<string> BusinessLogicAsync(string input)
        {
            Debug.WriteLine("Invoking business logic.");
            string returnValue = await DeeperBusinessLogicAsync();
            Debug.WriteLine("Business logic completed.");
            return input+returnValue;
        }

        // Here's a simulated deeper async call
        private static async Task<string> DeeperBusinessLogicAsync()
        {
            Debug.WriteLine("Invoking deeper business logic.");
            await Task.Delay(1000); // This simulates a long-running async process
            Debug.WriteLine("Deeper business logic completed.");
            return DateTime.Now.ToString();
        }
    }
}

【讨论】:

【解决方案2】:

这是因为await 默认情况下会捕获当前的SynchronizationContext 并在完成后尝试回发给它。这对于 ASP.NET 线程细节来说有点冒险,但简而言之,死锁实际上是在阻止线程的方法之间,而继续尝试回发给它。

有更好的设计,但修复或破解是不尝试回发到捕获的SynchronizationContext。请注意,这是一个 hack,因为它将在碰巧执行Task 的任何线程上运行延续(方法的其余部分)。这通常是一个ThreadPool 线程。

不过,这将解决您的僵局。记住危险,我会建议更好的设计。

await Task.Delay(1000).ConfigureAwait(false);

【讨论】:

  • 为什么回帖到当前的SynchronizationContext“让 ASP.NET 线程冒险”?如果 ASP.NET 无法处理来自同步上下文的延续,这是一个错误吗?如果在 ASP.NET 中使用 async/await 是危险的,那么为什么编译器不警告我我正在做一些愚蠢的事情?我想避免使用ConfigureAwait(false),因为我在我无法控制的库中有一系列嵌套的异步调用。我担心事情会破裂。你能推荐一个更好的具体设计吗?
  • ASP.NET 使用一个线程来处理请求。一个更好的设计应该是异步到顶层方法调用。我想目前情况并非如此。
  • 我给出的示例代码异步的。在服务器端,[WebMethod] 函数和它调用的 Task.Delay 都是异步函数;并且函数调用是使用 await 关键字进行的。此异步 C# 代码使用 PageMethods 连接到客户端网页,PageMethods 也是异步的——尽管它使用回调而不是 async/await 习惯用法。问题似乎是 PageMethods 调用正在死锁异步函数使用的延续,这些函数不会立即返回。我猜这是因为 PageMethods 已经锁定了 SynchronizationContext。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-01-13
  • 2010-11-27
  • 1970-01-01
  • 2013-08-17
  • 2020-01-12
  • 1970-01-01
  • 2020-11-28
相关资源
最近更新 更多