通过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