我有以下测试WebAPI代码,我没有在生产中使用WebAPI但我之所以这样做是因为我对这个问题进行了讨论:WebAPI异步问题
无论如何,这是违规的WebAPI方法:
public async Task<string> Get(int id)
{
var x = HttpContext.Current;
if (x == null)
{
// not thrown
throw new ArgumentException("HttpContext.Current is null");
}
await Task.Run(() => { Task.Delay(500); id = 3; });
x = HttpContext.Current;
if (x == null)
{
// thrown
throw new ArgumentException("HttpContext.Current is null");
}
return "value";
}
Run Code Online (Sandbox Code Playgroud)
我曾经相信第二个异常是预期的,因为当await完成时,它可能会在一个不同的线程上,HttpContext.Current因为线程静态变量将不再解析为适当的值.现在,基于同步上下文,实际上它可能会在等待之后被强制返回到同一个线程但我在测试中没有做任何奇特的事情.这只是一个简单,天真的用法await.
在另一个问题的评论中,我被告知HttpContext.Current在等待之后应该解决.对这个问题甚至还有另一个评论,表明同样的问题.什么是真的?应该解决吗?我想不,但我想要一个权威的答案,因为async它await是新的,我找不到任何确定的东西.
TL; DR:HttpContext.Current可能null在一个await?
我经常使用async/await来确保ASP.NET MVC Web API线程不被长时间运行的I/O和网络操作阻止,特别是数据库调用.
该System.Data.Entity的命名空间提供各种帮助这里的扩展,如FirstOrDefaultAsync,ContainsAsync,CountAsync等等.
但是,由于数据上下文不是线程安全的,这意味着以下代码存在问题:
var dbContext = new DbContext();
var something = await dbContext.someEntities.FirstOrDefaultAsync(e => e.Id == 1);
var morething = await dbContext.someEntities.FirstOrDefaultAsync(e => e.Id == 2);
Run Code Online (Sandbox Code Playgroud)
事实上,我有时会看到例外情况:
System.InvalidOperationException:未关闭连接.连接的当前状态是打开的.
那么正确的模式是否using(new DbContext...)为每个对数据库的异步调用使用单独的块?相反,执行同步可能更有益吗?
下面的代码已添加到新创建的Visual Studio 2012 .NET 4.5 WebAPI项目中.
我正在尝试分配两者HttpContext.Current.User并Thread.CurrentPrincipal在异步方法中.Thread.CurrentPrincipal流量分配不正确,除非执行await Task.Yield();(或其他任何异步)(传递true到AuthenticateAsync()将导致成功).
这是为什么?
using System.Security.Principal;
using System.Threading.Tasks;
using System.Web.Http;
namespace ExampleWebApi.Controllers
{
public class ValuesController : ApiController
{
public async Task GetAsync()
{
await AuthenticateAsync(false);
if (!(User is MyPrincipal))
{
throw new System.Exception("User is incorrect type.");
}
}
private static async Task AuthenticateAsync(bool yield)
{
if (yield)
{
// Why is this required?
await Task.Yield();
}
var principal = new MyPrincipal();
System.Web.HttpContext.Current.User …Run Code Online (Sandbox Code Playgroud) 此问题已由EF数据上下文 - 异步/等待和多线程触发.我已经回答了那个,但没有提供任何最终的解决方案.
最初的问题是,有许多有用的.NET API(如Microsoft Entity Framework DbContext),它们提供了设计用于的异步方法await,但它们被记录为不是线程安全的.这使它们非常适合在桌面UI应用程序中使用,但不适用于服务器端应用程序.[编辑] 这可能实际上并不适用DbContext,这是微软关于EF6线程安全的声明,自己判断. [/ EDITED]
还有一些已建立的代码模式属于同一类别,例如调用WCF服务代理OperationContextScope(在此处和此处询问),例如:
using (var docClient = CreateDocumentServiceClient())
using (new OperationContextScope(docClient.InnerChannel))
{
return await docClient.GetDocumentAsync(docId);
}
Run Code Online (Sandbox Code Playgroud)
这可能会失败,因为OperationContextScope在其实现中使用线程本地存储.
问题的根源AspNetSynchronizationContext是在异步ASP.NET页面中使用,以便从ASP.NET线程池中使用更少的线程来满足更多HTTP请求.使用时AspNetSynchronizationContext,await可以在与启动异步操作的线程不同的线程上对延续进行排队,同时将原始线程释放到池中,并可用于提供另一个HTTP请求.这大大提高了服务器端代码的可扩展性.该机制在It's All About the SynchronizationContext中有详细描述,必须阅读.因此,虽然没有涉及并发API访问,但潜在的线程切换仍然阻止我们使用上述API.
我一直在考虑如何在不牺牲可扩展性的情况下解决这个问题.显然,恢复这些API的唯一方法是维护可能受线程切换影响的异步调用范围的线程关联.
假设我们有这样的线程亲和力.这些调用中的大多数都是IO绑定的(没有线程).当异步任务处于挂起状态时,它所源自的线程可用于提供另一个类似任务的延续,该结果已经可用.因此,它不应该过多地损害可伸缩性.这种方法并不新鲜,事实上,Node.js成功使用了类似的单线程模型 …
我想弄清楚是否应该在顶级请求上使用ConfigureAwait(false).从这个主题的某个权威读取这篇文章:http: //blog.stephencleary.com/2012/07/dont-block-on-async-code.html
......他推荐这样的东西:
public async Task<JsonResult> MyControllerAction(...)
{
try
{
var report = await _adapter.GetReportAsync();
return Json(report, JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
return Json("myerror", JsonRequestBehavior.AllowGet); // really slow without configure await
}
}
public async Task<TodaysActivityRawSummary> GetReportAsync()
{
var data = await GetData().ConfigureAwait(false);
return data
}
Run Code Online (Sandbox Code Playgroud)
...它表示在除顶级调用之外的每个await上使用ConfigureAwait(false). 但是,在执行此操作时,我的异常需要几秒钟才能返回调用者而不是使用它并让它立即返回.
调用异步方法的MVC控制器操作的最佳实践是什么?我应该在控制器本身中使用ConfigureAwait,还是只在使用等待请求数据等的服务调用中使用?如果我不在顶级调用中使用它,等待几秒钟的异常似乎有问题.我不需要HttpContext,并且我已经看过其他帖子,如果你不需要上下文,那么总是使用ConfigureAwait(false).
更新: 我在我的调用链中某处丢失了ConfigureAwait(false),导致异常无法立即返回.但是问题仍然存在,是否应该在顶层使用ConfigureAwait(false).
我有一个类似的方法:
public async Task SaveItemsAsync(IEnumerable<MyItem> items)
{
using (var ts = new TransactionScope())
{
foreach (var item in items)
{
await _repository.SaveItemAsync(item);
}
await _repository.DoSomethingElse();
ts.Complete();
}
}
Run Code Online (Sandbox Code Playgroud)
这当然有问题,因为TransactionScopeasync/await不能很好用.
它失败并显示InvalidOperationException消息:
"TransactionScope必须放在与它创建的同一个线程上."
我TransactionScopeAsyncFlowOption在这个答案中读到了,这似乎正是我所需要的.
但是,对于这个特定项目,我很难支持.Net 4.0,无法升级到4.5或4.5.1.因此,我的项目中的异步/等待行为由Microsoft.Bcl.Async NuGet包提供.
我似乎无法TransactionScopeAsyncFlowOption在这个或任何其他OOB包中找到.我只是在某个地方错过了吗?
如果没有,是否有其他方法可以达到相同的效果?也就是说 - 我希望事务范围正确完成或回滚,尽管跨越线程有延续.
我DoSomethingElse在上面的示例中添加了说明在事务范围内可能有多个调用,因此在一次调用中简单地将所有项目传递给数据库是不可行的选择.
在它的事项的情况下,库使用直接ADO.Net( ,SqlConnection,SqlCommand等)写入到SQL Server.
UPDATE
我以为我有一个解决方案,涉及从.Net 4.5.1中获取System.Transactions.dll并将其包含在我的项目中.但是,我发现这只适用于我的开发盒,因为它已经安装了4.5.1.部署到仅具有.Net 4.0的计算机时,它不起作用.它只是给了一个MissingMethodException.我正在寻找一种适用于.Net 4.0安装的解决方案.
我目前正在开发一个WCF服务,该服务可以通过其他服务在一些操作中提交信息.第二个服务的代理是通过强类型ProxyFactory<T>类生成的.我没有遇到任何问题,但听到我在拨打电话时应该执行以下操作:
using (new OperationContextScope((IContextChannel)_service))
_service.Send(message);
Run Code Online (Sandbox Code Playgroud)
所以我的问题是:什么时候创建这个新的OperationContextScope合适的,为什么?
谢谢!
AspNetSynchronizationContext是最奇怪的实现.它将处理Post为同步而非异步,并使用锁定一次执行一个委托.
同样,他在同步上下文中写的文章与该评论中的链接表明:
从概念上讲,AspNetSynchronizationContext的上下文很复杂.在异步页面的生命周期中,上下文仅从ASP.NET线程池中的一个线程开始.异步请求启动后,上下文不包含任何线程.当异步请求完成时,执行其完成例程的线程池线程进入上下文.这些线程可能与发起请求的线程相同,但更可能是在操作完成时任何线程都是空闲的.
如果同一应用程序同时完成多个操作,AspNetSynchronizationContext将确保它们一次执行一个.它们可以在任何线程上执行,但该线程将具有原始页面的标识和文化.
挖掘反射器似乎验证了这一点,因为它HttpApplication在调用任何回调时需要锁定.
锁定app对象看起来像是可怕的东西.所以我的第一个问题:这是否意味着今天,整个应用程序的所有异步完成都会一次执行一个,即使是源自具有单独HttpContexts的单独线程上的单独请求的那些?对于任何100%使用异步页面(或MVC中的异步控制器)的应用程序来说,这不是一个巨大的瓶颈吗?如果没有,为什么不呢?我错过了什么?
此外,在.NET 4.5中,它看起来像是一个新的AspNetSynchronizationContext,旧的重命名LegacyAspNetSynchronizationContext,仅在UseTaskFriendlySynchronizationContext未设置新的应用程序设置时使用.问题2:新实现是否会改变这种行为?否则,我想通过同步上下文新的async/await支持编组完成,这种瓶颈会更频繁地被注意到.
这个论坛帖子的答案(从SO答案链接到这里)表明这里有一些根本改变的东西,但我想明确这是什么以及改进了什么行为,因为我们有一个.NET 4 MVC 3应用程序100%异步操作方法进行Web服务调用.
我有一个Web API应用程序,其中控制器通过依赖注入(Unity)将服务/存储库等注入其中.让我们假设我IStuffService需要IPrincipal当前请求(或它周围的包装).
Web API的问题似乎是当前Request/User 的唯一可靠来源是Request该实例上的属性ApiController.任何静态的(无论是HttpContext.Current,CallContext.Get/SetData或Thread.Get/SetData)不能保证是相同的线程上,由于网络API的同步性质.
如何可靠地确保特定于请求的上下文通过依赖项传递,更重要的是,操作在整个操作过程中保持正确IPrincipal?
两种选择:
IPrincipal都将它作为方法的参数 - 这是最可靠的方法,但它也要求我在每个方法签名中都有这个东西container.Resolve(opType, new DependencyOverride(typeof(IPrincipal), principal))选项2意味着我的方法签名是干净的,但这也意味着我需要确保所有依赖项都使用TransientLifetimeManager,而不是Singleton甚至是Per-Thread.
有没有比我没有看到更好的解决方案?
我试图阅读异步方法,现在我正在尝试创建自己的异步方法.该方法是一个webservice调用,它返回错误日志列表.我不确定我是否理解正确,所以我想我会分享我的代码,看看我是否应该做任何不同的事情.
我想要的代码就是通过调用方法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;
}
Run Code Online (Sandbox Code Playgroud)
谢谢!
With C#5 Async-Await in WCF, after an await if rest of the code continues on a different thread, we loose the Current Operation Context. (OperationContext.Current is null).
I am working on a WCF Service which calls another external service. And there are a few Custom Binding Extensions used in the external service call which access the Operation Context. So I need the Context to be propagated during this call and it cant just work with copying the operation context into …
我正在为Android应用程序编写WCF服务.流程非常简单:
我想它必须是我从未在C#中使用过的异步任务和线程.寻找一个简单的例子,我在复杂的教程和丰富的机会中丢失了C#中的任务和线程可以完成的任务.在这种情况下,最佳做法是什么?非常简单的例子将非常感激.
UPD.该服务使用Framework 4.0
c# ×11
async-await ×10
.net ×5
asp.net ×4
asynchronous ×3
asp.net-mvc ×2
wcf ×2
.net-4.0 ×1
azure ×1
c#-5.0 ×1
service ×1