WCF REST 不异步处理

本文关键字:处理 异步 REST WCF | 更新日期: 2023-09-27 17:56:18

我们目前正在为我们的网站在IIS中实现一个新的WCF REST服务,在许多页面上,我们可能会使用JQuery异步进行一些AJAX调用。问题是 WCF(在服务器端)似乎正在同步执行。

在页面加载时,我们对 3 个不同的方法进行了 3 次单独的调用。 使用日志记录,我可以看到它们都在大约 5 毫秒内命中 global.asax 文件。 从那里,日志记录显示所有执行的顺序,它们退出global.asax(不一定是我们通过javascript从页面进行调用的顺序)。 我希望每个调用都能收到自己的线程并单独返回。 即使使用调试器附加,我也可以看到它不会执行下一个方法,直到我单步执行它所在的当前方法。

以下是我"认为"我实现的三种使用异步模型的方法的操作契约。

    [OperationContract(AsyncPattern = true)]
    [WebInvoke(
        Method = "POST"
         , UriTemplate = "/ListUserPreferences"
        , BodyStyle = WebMessageBodyStyle.Wrapped
        , ResponseFormat = WebMessageFormat.Json
        , RequestFormat = WebMessageFormat.Json
    )]
    IAsyncResult BeginListUserPreferences(AsyncCallback callback, object state);
    Result<List<Data.EnumerationItem<UserPreferenceType>>> EndListUserPreferences(IAsyncResult asyncResult);
    [OperationContract(Name = "GetUserSecure", AsyncPattern = true)]
    [WebInvoke(
        Method = "POST"
         , UriTemplate = "/GetUser"
        , BodyStyle = WebMessageBodyStyle.Wrapped
        , ResponseFormat = WebMessageFormat.Json
        , RequestFormat = WebMessageFormat.Json
    )]
    IAsyncResult BeginGetUser(AsyncCallback callback, object state);
    Result<Data.User> EndGetUser(IAsyncResult asyncResult);
    [OperationContract(AsyncPattern = true)]
    [WebInvoke(
        Method = "POST"
         , UriTemplate = "/ListWithAttributes"
        , BodyStyle = WebMessageBodyStyle.Wrapped
        , ResponseFormat = WebMessageFormat.Json
        , RequestFormat = WebMessageFormat.Json
    )]
    IAsyncResult BeginListWithAttributes(int index, int pageSize, AsyncCallback callback, object state);
    Result<PagedCollection<Data.Attribute>> EndListWithAttributes(IAsyncResult asyncResult);

下面是服务中实现之一的示例。

    public IAsyncResult BeginGetUser(AsyncCallback callback, object state)
    {
        var asyncResult = new CompletedAsyncResult<Result<Data.User>>(state);
        asyncResult.Result = new Result<Data.User>();
        asyncResult.Result.Value.UserId = Guid.Empty;
        asyncResult.Result.Value.DisplayName = "asdfasd";
        asyncResult.IsCompleted = true;           
        callback(asyncResult);
        return asyncResult;
    }
    public Result<Data.User> EndGetUser(IAsyncResult asyncResult)
    {
        return ((CompletedAsyncResult<Result<Data.User>>)asyncResult).Result;
    }

以下是我们在服务实现类上的属性。

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Multiple)]

任何人都可以提供一些见解,说明为什么这些是同步执行的以及我需要做什么,或者至少指出我需要做什么的方向,让这些异步执行?

更新

我接受了 Matt 的一些答案,并将我的逻辑移到了 End 函数调用,并按照这篇博客文章介绍了他如何更紧密地将大文件上传到自承载 WCF Rest 服务。 但是,我无法使用他的技术调用 End 方法。 我也开始认为我这样做的方式是错误的。 因为查看日志,我的自定义身份验证服务在每个服务调用之前执行,这发生在任何异步方法操作甚至触发之前。 经过进一步调查,我查看了进入IIS的每个请求的ThreadId,然后执行操作。 WCF 似乎只使用 1 个工作线程...时期。 我一次向 IIS 发送多少个请求并不重要。 例如,如果我发送 3 个请求,我可以看到它们都在不同的时间(彼此在几毫秒内)传入,并且都获得了自己的线程。 但是,WCF 似乎只是将它们全部排队并按该顺序执行它们,因为它们都在同一线程上执行,包括身份验证服务调用。

WCF REST 不异步处理

在我看来,从您的示例中,您正在"开始"调用完成之前完成所有工作; 这是我在学习异步模式时发现的常见错误。

虽然我不熟悉它在 WCF 中的应用程序,但异步模型更典型的是 Begin 方法,该方法启动一些新线程,其中包含要由该新线程更新的IAsyncResult对象。 为了"完成"操作,当IsCompleted设置为true时,原始调用者应将原始IAsyncResult对象传递回返回结果的相应 End 方法。 一个简单的实现如下所示:

    static Func<string> getUser;
    public static IAsyncResult BeginGetUser(AsyncCallback callback, object state)
    {
        getUser = () =>
            {
                Thread.Sleep(2000);
                return "finished";
            };
        return getUser.BeginInvoke(callback, state);
    }
    public static string EndGetUser(IAsyncResult asyncResult)
    {
        return getUser.EndInvoke(asyncResult);
    }

对它的调用可能如下所示:

var result = BeginGetUser(null, null);
string value = EndGetUser(result);

当然,这是一个微不足道的情况:引用 http://kennyw.com/work/indigo/258 的话,"如果你没有做一些"原生异步"的事情,那么你不应该使用 AsyncPattern=true"。

幸运的是,随着 C# 5.0 或Microsoft发布的异步 CTP,.Net 异步模式可能会成为过去。

为了使事情异步,你通常把它与其他异步的东西组合在一起。 如果您只使用异步方法执行同步操作,那么使用异步模式没有任何意义。 例如,如果你的代码所做的只是把工作放在线程池上,你没有完成任何事情,因为你已经通过异步移动代码将线程交还给了线程池,但通过在那里启动你的工作,直接从 ASP.NET 窃取了它。

如果要进行数据库调用以获取用户,并且数据库支持异步操作,则可以生成如下所示的内容:

public IAsyncResult BeginGetUser(AsyncCallback callback, object state)
{
  var taskFunc = Task<DbReader>.Factory.FromAsync(db.BeginGetUser, db.EndGetUser);
  return taskFunc.ContinueWith(task => {
    var reader = task.Result;
    reader.Read();
    return new Data.User {
      DisplayName = reader["displayName"] as string,
      UserId = Guid.Parse(reader["userId"] as string),
    }
   }
  );
}
public Result<Data.User> EndGetUser(IAsyncResult asyncResult)
{
  return (Task<User>)(asyncResult).Result;
}

让我强调一点:异步编程很棘手。 使用任务,它会变得容易一些,但您仍然需要围绕延续进行思考。 调试是一件苦差事,一个错误的举动,你的代码就消失了,你不知道为什么。 如果您错过了异常,它将出现在终结器线程上,您不一定知道原因。 此外,为了进行异步编程,您必须小心翼翼地进入充满危险的多线程编程。

在尝试异步之前,请确保您真正了解正在发生的事情。 如果你的代码本质上是同步的,那么让它异步不会给你带来太多好处,除非它是一个长时间运行的进程,你可以启动另一个线程来处理它。 人们使用异步处理程序,以便 IIS(或任何底层 Web 服务器)可以将其线程恢复为其他请求提供服务;正如我之前提到的,如果你只是从线程池中取出一个线程,你就是从IIS中窃取该线程,你不会看到可伸缩性的任何收益。 但是,如果您只是疯狂地创建新线程,则会使自己受到拒绝服务攻击。