.net 在WCF服务中使用async/await时,OperationContext.Current在首次等待后为null
我正在使用.NET4.5中的async/await模式在WCF中实现一些服务方法。 示例服务: 合同:.net 在WCF服务中使用async/await时,OperationContext.Current在首次等待后为null,.net,wcf,.net-4.5,async-await,.net,Wcf,.net 4.5,Async Await,我正在使用.NET4.5中的async/await模式在WCF中实现一些服务方法。 示例服务: 合同: [ServiceContract(Namespace = "http://async.test/")] public interface IAsyncTest { Task DoSomethingAsync(); } 实施: MyAsyncService : IAsyncTest { public async Task DoSomethingAsync() {
[ServiceContract(Namespace = "http://async.test/")]
public interface IAsyncTest
{
Task DoSomethingAsync();
}
实施:
MyAsyncService : IAsyncTest
{
public async Task DoSomethingAsync()
{
var context = OperationContext.Current; // context is present
await Task.Delay(10);
context = OperationContext.Current; // context is null
}
}
我遇到的问题是,在第一次之后等待操作上下文。当前返回null
,我无法访问操作上下文。当前.IncomingMessageHeaders
在这个简单的示例中,这不是问题,因为我可以在wait
之前捕获上下文。但是在现实世界中,OperationContext.Current
是从调用堆栈的深处访问的,我真的不想为了进一步传递上下文而更改大量代码
有没有一种方法可以在wait
点之后获取操作上下文,而不用手动将其传递到堆栈中?我认为最好的选择是实际捕获并手动传递它。您可能会发现这提高了代码的可测试性
也就是说,还有几个其他选择:
将其添加到LogicalCallContext
安装您自己的SynchronizationContext
,它将在执行Post
时设置OperationContext.Current
;这就是ASP.NET如何保存其HttpContext.Current
安装您自己的TaskScheduler
,它设置OperationContext.Current
您可能还想在Microsoft Connect上提出此问题。不幸的是,此问题无法解决,我们将在将来的版本中考虑解决此问题
同时,有一种方法可以将上下文重新应用于当前线程,这样就不必传递对象:
public async Task<double> Add(double n1, double n2)
{
OperationContext ctx = OperationContext.Current;
await Task.Delay(100);
using (new OperationContextScope(ctx))
{
DoSomethingElse();
}
return n1 + n2;
}
公共异步任务添加(双n1,双n2)
{
OperationContext ctx=OperationContext.Current;
等待任务。延迟(100);
使用(新OperationContextScope(ctx))
{
DoSomethingElse();
}
返回n1+n2;
}
在上面的示例中,DoSomethingElse()方法可以访问OperationContext.Current,正如预期的那样。幸运的是,我们的实际服务实现是通过Unity
IoC容器实例化的。这使我们能够创建一个IWCOperationContext
,它被配置为具有PerResolveLifetimeManager
,这意味着对于我们的RealService
的每个实例,只有一个wcfoOperationContext
实例
在wcfooperationcontext
的构造函数中,我们捕获OperationContext.Current
,然后从iwcfooperationcontext
获取所有需要它的位置。这实际上是Stephen Cleary在他的回答中建议的。下面是一个示例同步上下文
实现:
public class OperationContextSynchronizationContext : SynchronizationContext
{
private readonly OperationContext context;
public OperationContextSynchronizationContext(IClientChannel channel) : this(new OperationContext(channel)) { }
public OperationContextSynchronizationContext(OperationContext context)
{
OperationContext.Current = context;
this.context = context;
}
public override void Post(SendOrPostCallback d, object state)
{
OperationContext.Current = context;
d(state);
}
}
使用方法:
var currentSynchronizationContext = SynchronizationContext.Current;
try
{
SynchronizationContext.SetSynchronizationContext(new OperationContextSynchronizationContext(client.InnerChannel));
var response = await client.RequestAsync();
// safe to use OperationContext.Current here
}
finally
{
SynchronizationContext.SetSynchronizationContext(currentSynchronizationContext);
}
扩展Cleary先生的#1选项,可以将以下代码放在WCF服务的构造函数中,以存储和检索逻辑调用上下文中的OperationContext
:
if (CallContext.LogicalGetData("WcfOperationContext") == null)
{
CallContext.LogicalSetData("WcfOperationContext", OperationContext.Current);
}
else if (OperationContext.Current == null)
{
OperationContext.Current = (OperationContext)CallContext.LogicalGetData("WcfOperationContext");
}
这样,无论您在任何地方遇到空上下文问题,都可以编写如下内容:
var cachedOperationContext = CallContext.LogicalGetData("WcfOperationContext") as OperationContext;
var user = cachedOperationContext != null ? cachedOperationContext.ServiceSecurityContext.WindowsIdentity.Name : "No User Info Available";
免责声明:这是一段已有一年历史的代码,我不记得在构造函数中需要else的原因,但这与async有关,我知道在我的情况下需要它。更新:正如下面评论中指出的,此解决方案不是线程安全的,所以我想上面讨论的解决方案仍然是最好的方法
我通过将HttpContext注册到我的DI容器(Application_BeginRequest)中来解决这个问题,并在需要时解决它
登记册:
this.UnityContainer.RegisterInstance<HttpContextBase>(new HttpContextWrapper(HttpContext.Current));
this.UnityContainer.RegisterInstance(新的HttpContextWrapper(HttpContext.Current));
决心:
var context = Dependencies.ResolveInstance<HttpContextBase>();
var context=Dependencies.ResolveInstance();
它似乎在.Net 4.6.2中得到了修复。请参见将任务
实例通过连线序列化到客户端意味着什么?这很有趣。不过,我不确定您是否真的希望以这种方式执行操作。当操作因某种原因失败时,您会怎么做?客户认为它成功了。您最好在某种类型的事务队列中对这些操作进行排队。@Steven:WCF运行时在任务完成之前不会向客户端返回响应。@chrismaric感谢您关于在WCF服务中不使用异步的评论。在这里解决了我的问题:@MikeTaverne是的,这正是不在内部使用异步的原因。处理线程切换丢失的范围和上下文是疯狂的。Jon-你显然比大多数代表16岁的人知道得多(我在MSDN上找到了你的博客)。我可以建议您更新您的个人资料,以便人们了解您的答案的质量。Jon,我们只是想确定一种解决此问题的方法,我想知道您是否可以解释为什么在上面的代码中使用OperationContextScope,而不是直接使用ctx?(我在这里发布了更多详细信息)使用OperationContextScope的主要原因是将OperationContext.Current设置为传递给构造函数的设置。这可以防止您将ctx实例向下传递到深层调用堆栈(例如,您不必修改方法签名以获取OperationContext参数)。此解决方案不允许从等待中访问OperationContext.Current。在上面的例子中,是的,它是有效的。凯文是对的。当调用位于await方法内时,此解决方案没有帮助。如果存在嵌套级别的等待调用,则不会替换当前SynchronizationContext。因此,我必须通过检查扩展重写的Post方法,如果SynchronizationContext.Current为null,我将调用SynchronizationContext.SetSynchronizationContext(此)。我不知道这是否是正确的方法,但它对我来说是有效的。我怀疑这是否正确