【问题标题】:Creating an async webservice method创建异步 Web 服务方法
【发布时间】:2013-08-24 09:26:23
【问题描述】:

我已尝试阅读异步方法,现在正在尝试创建自己的异步方法。该方法是一个返回错误日志列表的 Web 服务调用。我不确定我是否理解正确,所以我想我会分享我的代码,看看我是否应该做些不同的事情。

我想要代码做的只是通过调用方法GetAllErrorLogs()返回一个错误日志列表,这是一个同步方法。由于获取所有错误日志可能需要一秒钟,因此我希望在调用 GetAllErrorLogs() 方法后有机会做其他事情。这是代码。

[WebMethod]
public async Task<List<ErrorLog>> GetAllErrorLogs()
{
    List<ErrorLog> errorLogs = new List<ErrorLog>();

    await System.Threading.Tasks.Task.Run(() => {
        errorLogs = ErrorLogRepository.GetAllErrorLogs();
    });


    if (errorLogs == null)
        return new List<ErrorLog>();

    return errorLogs;
}

谢谢!

【问题讨论】:

  • 我看不出在服务器端使用 async/await 有多大优势。您只需对同一件事使用更多线程。
  • @I4V: async 在服务器端可以显着减少每个请求使用的线程数(假设代码自然是异步的,而不是像@这样的伪异步987654324@)。因此,异步服务器能够更好地扩展,通常为 10-100 倍。
  • Stephan Cleary 是正确的...我在微软校园参加了一个培训课程,在那里我们被告知,只要异步运行在服务器上的所有代码,您就可以大幅增加相同的容量硬件。纯粹是因为,当一个方法在等待子调用的响应时,主线程被释放去做其他工作......即处理其他并发的网络请求。优点是多任务处理。

标签: c# .net async-await


【解决方案1】:

我最近在ThatConferenceasync on the server side 上做了一次演讲,我在幻灯片中解决了这个问题。

在服务器端,您要避免使用Task.Run 和其他将工作排队到线程池的构造。 尽可能保持线程池线程可用于处理请求。

因此,理想情况下,您的存储库应该有一个异步方法GetAllErrorLogsAsync,它本身就是异步的。如果GetAllErrorLogs不能异步,那你也可以直接调用(去掉await Task.Run)。

由于获取所有错误日志可能需要一秒钟,因此我希望在调用 GetAllErrorLogs() 方法后有机会做其他事情。

如果您有可用的GetAllErrorLogsAsync,则可以使用Task.WhenAll 轻松完成。但是,如果 GetAllErrorLogs 是同步的,那么您只能通过在请求中执行并行工作来做到这一点(例如,多次调用 Task.Run 后跟 Task.WhenAll)。

必须怀着极大的恐惧来处理服务器上的并行代码。它只在非常有限的场景中是可以接受的。服务器端async 的全部意义在于每个请求使用更少 个线程,而当您开始并行化时,您正在做相反的事情:每个请求多个 个线程.这仅适用于您知道您的用户群非常小的情况;否则,您将扼杀服务器的可扩展性。

【讨论】:

  • 感谢您的回答!
  • 并行的复杂性对于这里和题外话来说太长了,但是应该注意这里对并行的警告过于宽泛。如果您正在等待对 Web 服务和数据库调用的 Web 请求,则您正在利用 I/O 完成端口线程,这些线程非常便宜,操作系统级别,并且不吃线程池中的线程。一个完美的并行方案,可以为用户加快处理速度,而不会破坏请求线程池。这里的正确答案是“这里是如何”而不是“不要这样做”。 stackoverflow.com/a/539968/176877
  • @ChrisMoschini:我区分了 多线程/并行Task.RunParallel 等)和 异步asyncawait 等),它们都是different forms of concurrency。使用这些定义,服务器端的并行性很差。
  • @ChrisMoschini:随意发布您自己的答案以及对我的投反对票。 :)
【解决方案2】:

我发现这篇很棒的代码项目详细文章介绍了如何实现这一目标

http://www.codeproject.com/Articles/600926/Asynchronous-web-services-call-in-ASP-NET

【讨论】:

    【解决方案3】:

    **这可能是错误的,请阅读 HttpContext.Current after an await 的 cmets 或衍生问题

    如果ErrorLogRepository.GetAllErrorLogs() 不是线程安全的,它会导致奇怪的错误和潜在的异常。在切换到异步方法之前,请确保您的代码已准备好进行多线程操作,这显然是非常琐碎的建议,但经常被忽视。例如,如果您在方法中引用HttpContext.Current,您的代码将在异步方法中死掉,有时甚至在await 之后。原因是异步块中的代码可能会在单独的线程上运行,该线程无法访问相同的HttpContext.Current 线程静态属性,并且await 被编译成两个方法。 await 之前的所有代码都在一个线程上运行,然后在 await 关键字之后调用代码作为延续,但可能在另一个线程上。因此,有时您的代码甚至可以在异步块中工作,只是在它“退出”异步后意外阻塞,回到您认为的代码的同步部分(但实际上await 关键字之后的所有内容都已经不保证是原始线程)。

    【讨论】:

    • 这个答案有很多错误信息。 UnobservedTaskException 在这里不是必需的(await 将通过 WebAPI 方法正确传播来自 GetAllErrorLogs 的任何异常)。默认情况下,HttpContext.Current 会正确传播到处理异步请求的所有线程(即return errorLogs 行具有完全有效的HttpContext.Current)。
    • 你是对的等待,我误读了那部分。只有在 Task.Run 被用作火而忘记的情况下才不会传播它。但是,除非 WebAPI 覆盖了默认的同步上下文行为,否则 HttpContext.Current 在等待之后肯定会无效。
    • WebAPI 不提供同步上下文,但 ASP.NET 提供。所以HttpContext.Currentawait 之后完全有效。
    • @StephenCleary,WebAPI 不提供同步上下文 - 嗯,在任何情况下它仍然是 AspNetSynchronizationContext 用于 Web API 吗?这是related WebAPI question,我向 OP 询问了Debug.Print(SynchronizationContext.Current.GetType().Name),它是AspNetSynchronizationContext。到目前为止,我一直认为这是 Web API 的默认行为。
    • @Noseratio:这是默认行为。这就是 WebAPI 使用的 ASP.NET 提供的同步上下文。
    【解决方案4】:

    这是一些生产代码...

    using System.Web.Http;
    using AysncTask = System.Threading.Tasks.Task;
    
    public class myController : ApiControllerBase
    {
            [HttpPut]
            [Route("api/cleardata/{id}/{requestId}/")]
            public async AysncTask ClearData(Guid id, Guid requestId)
            {
                try
                {
                    await AysncTask.Run(() => DoClearData(id, requestId));
                }
                catch (Exception ex)
                {
                    throw new Exception("Exception in myController.ClearData", ex);
                }
            }
    }
    

    【讨论】:

      【解决方案5】:

      处理异步异常也非常非常重要。虽然这是针对 Windows 控制台应用程序,但应该适用相同的原则。

      来源:https://blogs.msdn.microsoft.com/ptorr/2014/12/10/async-exceptions-in-c/

        using System;
        using System.Runtime.CompilerServices;
        using System.Threading;
        using System.Threading.Tasks;
      
        namespace AsyncAndExceptions
        {
      class Program
      {
        static void Main(string[] args)
        {
          AppDomain.CurrentDomain.UnhandledException += (s, e) => Log("*** Crash! ***", "UnhandledException");
          TaskScheduler.UnobservedTaskException += (s, e) => Log("*** Crash! ***", "UnobservedTaskException");
      
          RunTests();
      
          // Let async tasks complete...
          Thread.Sleep(500);
          GC.Collect(3, GCCollectionMode.Forced, true);
        }
      
        private static async Task RunTests()
        {
          try
          {
            // crash
            // _1_VoidNoWait();
      
            // crash 
            // _2_AsyncVoidAwait();
      
            // OK
            // _3_AsyncVoidAwaitWithTry();
      
            // crash - no await
            // _4_TaskNoWait();
      
            // crash - no await
            // _5_TaskAwait();
      
            // OK
            // await _4_TaskNoWait();
      
            // OK
            // await _5_TaskAwait();
          }
          catch (Exception ex) { Log("Exception handled OK"); }
      
          // crash - no try
          // await _4_TaskNoWait();
      
          // crash - no try
          // await _5_TaskAwait();
        }
      
        // Unsafe
        static void _1_VoidNoWait()
        {
          ThrowAsync();
        }
      
        // Unsafe
        static async void _2_AsyncVoidAwait()
        {
          await ThrowAsync();
        }
      
        // Safe
        static async void _3_AsyncVoidAwaitWithTry()
        {
          try { await ThrowAsync(); }
          catch (Exception ex) { Log("Exception handled OK"); }
        }
      
        // Safe only if caller uses await (or Result) inside a try
        static Task _4_TaskNoWait()
        {
          return ThrowAsync();
        }
      
        // Safe only if caller uses await (or Result) inside a try
        static async Task _5_TaskAwait()
        {
          await ThrowAsync();
        }
      
        // Helper that sets an exception asnychronously
        static Task ThrowAsync()
        {
          TaskCompletionSource tcs = new TaskCompletionSource();
          ThreadPool.QueueUserWorkItem(_ => tcs.SetException(new Exception("ThrowAsync")));
          return tcs.Task;
        }
        internal static void Log(string message, [CallerMemberName] string caller = "")
        {
          Console.WriteLine("{0}: {1}", caller, message);
        }
      }
      

      }

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-08-28
        • 2012-10-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多