Асинхронные функции в тупике C # при вызове из ASPX-страницы javascript через PageMethods - PullRequest
0 голосов
/ 01 мая 2018

Описание проблемы:
Я пытаюсь использовать PageMethods для вызова функции C # со страницы HTML. Проблема в том, что вызываемая мной функция 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();
        }
    }
}

Вопрос:
Мне абсолютно необходимо иметь возможность вызывать асинхронный код из пользовательского интерфейса моей веб-страницы. Я хотел бы использовать функциональность async / await для этого, но я не смог понять, как это сделать. В настоящее время я работаю над этим дефицитом, используя Task.Wait() и Task.Result вместо async / await, но это явно не рекомендуемое долгосрочное решение.
Как мне ожидать асинхронных функций на стороне сервера в контексте вызова PageMethods ???
Я действительно очень хочу понять, ЧТО здесь происходит, и ПОЧЕМУ этого НЕ происходит, когда асинхронный метод вызывается из консольного приложения.

Ответы [ 2 ]

0 голосов
/ 03 мая 2018

Я нашел подход, который позволяет PageMethods вызывать вложенные асинхронные функции в ASP.NET без блокировки. Мой подход включает

  1. Использование ConfigureAwait (false), поэтому мы не заставляем асинхронную функцию пытаться вернуться к исходному захваченному контексту (который поток веб-интерфейса будет заблокирован); и
  2. Использование асинхронной функции «верхнего уровня» в пуле потоков вместо ее запуска в контексте пользовательского интерфейса ASP.NET.

Каждый из этих двух подходов обычно рекомендуется использовать на форумах и в блогах, поэтому я уверен, что выполнение обоих является антипаттерном. Тем не менее, он позволяет использовать полезную оболочку для вызова асинхронных функций с веб-страницы ASP.NET с помощью PageMethods.
Пример кода 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();
        }
    }
}
0 голосов
/ 02 мая 2018

Это потому, что await по умолчанию захватывает текущий SynchronizationContext и пытается отправить его обратно после завершения. Это становится рискованным с деталями потоков ASP.NET, но вкратце тупик фактически находится между тем методом, блокирующим поток, в то время как продолжение пытается отправить его обратно.

Существуют лучшие дизайны, но исправление или взлом НЕ состоит в том, чтобы НЕ пытаться отправить обратно на захваченный SynchronizationContext. Обратите внимание, что это хак, потому что он будет запускать продолжение (остаток метода) в любом потоке, в котором он выполнил Task. Обычно это поток ThreadPool.

Однако это решит ваш тупик. Имейте в виду опасности, и я бы предложил лучший дизайн.

await Task.Delay(1000).ConfigureAwait(false);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...