WCF REST 不异步处理

发布于 2024-11-06 12:10:04 字数 3351 浏览 1 评论 0原文

目前,我们正在 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 似乎只是将它们全部排队并按顺序执行它们,因为它们都在同一线程上执行,包括身份验证服务调用。

We are currently implementing a new WCF REST service in IIS for our site and on a number of pages we may be making a handful of AJAX calls using JQuery asynchronously. The problem is that it seems as though WCF (on the server side) is executing synchronously.

On page load we're making 3 separate calls to 3 different methods. Using logging, I can see them all hit the global.asax file within about 5ms of each other. From there, the logging shows everything executing in the order they exit the global.asax (not necessarily the order in which we made the calls from the page via javascript). I expected each call to receive their own thread and return individually. Even when attaching with the debugger I can see that it won't execute the next method until I step through the current method it's on.

Here are the operation contracts for three of the methods I 'thought' I implemented to use the async model.

    [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);

Here is an example of one of the implementations in the service.

    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;
    }

Here are the attributes we have on the service implementation class.

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

Can anyone please provide some insight as to why these are executing synchronously and what I need to do, or at least point me in the direction of what I need to do, to get these to execute asynchronously?

UPDATE

I took some of Matt's answer and moved my logic to the End function calls and followed this blog post on how he did it a bit more closely Uploading Large Files To Self Hosted WCF Rest Service. However, I couldn't get the End methods to call using his technique. I'm also starting to think I'm going about this the wrong way. Because looking at the logs, my custom authentication service is being executed before each service call, which happens before any async method operations even fire. After further investigation, I took a look at the ThreadId's of each request coming into IIS and then executing the operations. It appears that WCF is only using 1 worker thread... period. It doesn't matter how many requests I send at a time to IIS. For example, if I send 3 requests, I can see they all come in at different times (within milliseconds of each other) and all get their own thread. But then it seems WCF just queues them all and executes them in that order because they're all executed on the same thread, including the authentication service calls.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

怪我鬧 2024-11-13 12:10:04

从你的例子来看,在我看来,你正在“开始”调用完成之前完成所有工作;这是我在学习异步模式时发现的一个常见错误。

虽然我不熟悉它在 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 发布的 Async CTP,.Net 异步模式可能会成为过去。

It looks to me, from your example, like you're doing all the work before the "Begin" call finishes; that's a common mistake I found while learning the async pattern.

Though I'm not familiar with its application in WCF, the Async model more typically is a Begin method that launches some new thread with a IAsyncResult object to be updated by that new thread. To "complete" the action, when IsCompleted is set to true, the original caller is expected to pass the original IAsyncResult object back to the corresponding End method which returns the result. A trivial implementation looks as follows:

    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);
    }

The calls to it might look like:

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

Of course, this is a trivial case: to quote http://kennyw.com/work/indigo/258, "If you aren’t doing something that’s "natively async", then you shouldn’t be using AsyncPattern=true".

Fortunately, with C# 5.0 or the Async CTP that Microsoft released, the .Net async pattern may become a thing of the past.

没有你我更好 2024-11-13 12:10:04

为了使事物异步,您通常将其与其他异步事物组合在一起。如果您仅使用异步方法执行同步操作,那么使用异步模式没有任何意义。例如,如果您的所有代码所做的只是将工作放在线程池上,那么您还没有完成任何事情,因为您通过异步移动代码将线程交还给线程池,但通过启动您的代码从 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;
}

让我强调一下:异步编程很棘手。使用 Task 会变得更容易一些,但您仍然需要专注于延续。调试是一件苦差事,一旦走错一步,你的代码就会消失,而你却不知道为什么。如果您错过了异常,它将出现在终结器线程上,您不一定知道原因。此外,为了进行异步编程,您必须小心翼翼地进行充满危险的多线程编程。

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

To make things asynchronous you generally compose it together with other things that are asynchronous. If you are only doing synchronous things with your asynchronous methods then it doesn't make any sense to use the async pattern. For example, if all your code does is put work on the thread pool you haven't accomplished anything because you've handed back the thread to the thread pool by moving your code asynchronous but stolen it right back from ASP.NET by launching your work there.

If you are making a database call to get the user, and your database supports asynchronous operations, you might build something like this:

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;
}

Let me stress something: Async programming is tricky. With Task it gets a little easier, but you still have to wrap your head around continuations. Debugging is a chore and one wrong move and your code just disappears and you don't know why. If you miss an exception it will appear on the finalizer thread and you'll not necessarily know why. Also, in order to do async programming you have to tip-toe into multi-threaded programming which is full of danger.

Make sure you really understand what is going on before attempting to go Asynchronous. If your code is synchronous by nature then making it asynchronous doesn't buy you very much unless it's a long-running process and you are OK spinning up another thread to handle it. People use asynchronous handlers so that IIS (or whatever underlying web server) can have its thread back to service other requests; as I mentioned before, if you then just go take a thread off the thread pool you are stealing that thread back from IIS and you won't see any gains in scalability. If you just wildly create new threads, however, you open yourself up to denial of service attacks.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文