通过PageMethods从javascript aspx页面调用时,C#死锁中的异步函数

通过PageMethods从javascript aspx页面调用时,C#死锁中的异步函数,c#,asp.net,asynchronous,async-await,pagemethods,C#,Asp.net,Asynchronous,Async Await,Pagemethods,问题摘要: 我正在尝试使用PageMethods从HTML页面调用C#函数。问题是我调用的C#函数被标记为异步,并将等待其他函数的完成。当PageMethods调用嵌套的异步C#函数时,C#代码似乎处于死锁状态。 我给出了一个ASP.NET页面的示例,后面用C#编码来说明我试图使用的习惯用法 示例WebForm1.aspx <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs" Inherits

问题摘要:
我正在尝试使用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>
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();
        }
    }
}
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();
        }
    }
}

函数GetTiming(){
log(“GetTiming函数已启动”);
PageMethods.GetFunctionTimeing(
函数(response、userContext、methodName){window.alert(response.Result);}
);
log(“GetTiming函数已结束”);//此行被命中!
}
示例WebForm1.aspx.cs

<%@ 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>
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();
        }
    }
}
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();
        }
    }
}
使用系统;
使用System.Threading.Tasks;
使用系统诊断;
使用System.Web.Services;
使用System.Web.UI;
命名空间WebApplication3
{
公共部分类WebForm1:第页
{
受保护的无效页面加载(对象发送方,事件参数e){}
[网络方法]
公共静态异步任务GetFunctionTiming()
{
string returnString=“开始时间:”+DateTime.Now.ToString();
WriteLine(“调用业务逻辑”);
等待任务。延迟(1000);//这似乎是死锁
//Task.Delay(1000.Wait();//如果没有注释,这个习惯用法就可以使用。
Debug.WriteLine(“业务逻辑完成”);//如果我们等待任务,这一行不会被击中!
return returnString+“\nEnd time:”+DateTime.Now.ToString();
}
}
}
问题:
我绝对需要能够从我的网页UI调用异步代码。我想使用async/await功能来实现这一点,但我还没有弄清楚如何实现。我目前正在通过使用
Task.Wait()
Task.Result
而不是async/Wait来解决这个问题,但这显然不是推荐的长期解决方案。
如何在PageMethods调用的上下文中等待服务器端异步函数

我真的,真的想了解在这里的封面下发生了什么,以及为什么从控制台应用程序调用异步方法时不会发生这种情况。

这是因为默认情况下,
wait
捕获当前的
SynchronizationContext
,并在完成后尝试发回。这对于ASP.NET线程的详细信息来说是危险的,但简言之,死锁实际上是在该方法阻塞线程,而continuation试图发回线程之间发生的

有更好的设计,但有一个修复方法,即黑客,就是不要试图发回捕获的
SynchronizationContext
。请注意,这是一个hack,因为它将在任何执行
任务的线程上运行continuation(方法的剩余部分)。这通常是一个
ThreadPool
线程

然而,这将解决您的死锁。记住这些危险,我建议一个更好的设计

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

我找到了一种方法,允许PageMethods调用ASP.NET上的嵌套异步函数,而不会出现死锁。我的方法包括

  • 使用ConfigureAwait(false),因此我们不会强制异步函数尝试返回原始捕获的上下文(Web UI线程将锁定该上下文);及
  • 将“顶级”异步函数强制到线程池中,而不是在ASP.NET的UI上下文中运行
  • 这两种方法中的每一种都是论坛和博客上经常推荐的,所以我确信这两种方法都构成了一种反模式。但是,它确实允许使用PageMethods从ASP.NET网页调用异步函数。
    示例C#代码如下所示。

    示例WebForm1.aspx.cs

    <%@ 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>
    
    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();
            }
        }
    }
    
    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();
            }
        }
    }
    
    使用系统;
    使用System.Threading.Tasks;
    使用系统诊断;
    使用System.Web.Services;
    使用System.Web.UI;
    命名空间WebApplication3
    {
    公共部分类WebForm1:第页
    {
    受保护的无效页面加载(对象发送方,事件参数e){}
    [网络方法]
    公共静态异步任务GetFunctionTiming()
    {
    WriteLine(“调用了Shim函数”);
    string returnString=“开始时间:”+DateTime.Now.ToString();
    //下面是一个惯用的垫片,它允许从PageMethods进行异步调用
    string myParameter=“\nEnd time:”;//我们要传递给业务逻辑的一些参数
    Task myTask=Task.Run(()=>BusinessLogicAsync(myParameter));//通过将任务强制到线程池中来避免死锁问题
    string myResult=await myTask.ConfigureAwait(false);//强制在当前(ASP.NET)上下文中继续
    Debug.WriteLine(“填隙函数已完成。将结果“+myResult+”返回到网站上的PageMethods调用…”);
    返回returnString+myResult;
    }
    //这将取代一些可能嵌套更深层次异步调用的复杂业务逻辑
    专用静态异步任务BusinessLogicAsync(字符串输入)
    {
    WriteLine(“调用业务逻辑”);
    string returnValue=await DeeperBusinessLogicAsync();
    WriteLine(“业务逻辑完成”);
    返回输入+返回值;
    }
    //下面是一个模拟的更深层次的异步调用
    专用静态异步任务DeeperBusinessLogicAsync()
    {
    WriteLine(“调用更深层的业务逻辑”);
    wait Task.Delay(1000);//这模拟了一个长期运行的异步进程
    WriteLine(“完成了更深层次的业务逻辑”);
    return DateTime.Now.ToString();
    }
    }
    }
    
    out of c