创建异步 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.Run
、Parallel
等)和 异步(async
、await
等),它们都是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.Current
在await
之后完全有效。
@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 服务方法的主要内容,如果未能解决你的问题,请参考以下文章