创建异步 Web 服务方法

Posted

技术标签:

【中文标题】创建异步 Web 服务方法【英文标题】:Creating an async webservice method 【发布时间】: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 是正确的...我在微软校园参加了一个培训课程,在那里我们被告知,只要异步运行在服务器上的所有代码,您就可以大幅增加相同的容量硬件。纯粹是因为,当一个方法在等待子调用的响应时,主线程被释放去做其他工作......即处理其他并发的网络请求。优点是多任务处理。 realted post 【参考方案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 完成端口线程,这些线程非常便宜,操作系统级别,并且不吃线程池中的线程。一个完美的并行方案,可以为用户加快处理速度,而不会破坏请求线程池。这里的正确答案是“这里是如何”而不是“不要这样做”。 ***.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);
  

【讨论】:

以上是关于创建异步 Web 服务方法的主要内容,如果未能解决你的问题,请参考以下文章

ASP.NET Web 表单 - 如何异步调用 WCF 异步方法?

使用 Web 服务的异步方法

如何使用 RESTful Web 服务创建异步通知系统?

使用 Moq 模拟单元测试的异步方法

从异步 Web 服务响应更新托管对象的最佳方法?

从 Web 服务下载异步数据并存储在核心数据中的最佳方法